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::ProgramCore,
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 particular case to run.
92#[derive(Clone, Debug, Default)]
93pub struct Case {
94    pub program_name: String,
95    pub function: String,
96    pub private_key: Option<String>,
97    pub input: Vec<String>,
98}
99
100/// The status of a case that was run.
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum ExecutionStatus {
103    None,
104    Aborted(Option<String>),
105    Accepted,
106    Rejected,
107    Halted(String),
108}
109
110impl fmt::Display for ExecutionStatus {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        match self {
113            Self::Halted(s) => write!(f, "halted ({s})"),
114            Self::None => write!(f, "none"),
115            Self::Aborted(None) => write!(f, "aborted"),
116            Self::Aborted(Some(reason)) => write!(f, "aborted: {reason}"),
117            Self::Accepted => write!(f, "accepted"),
118            Self::Rejected => write!(f, "rejected"),
119        }
120    }
121}
122
123#[derive(Debug, Clone)]
124pub enum EvaluationStatus {
125    Success,
126    Failed(String),
127}
128
129impl fmt::Display for EvaluationStatus {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        match self {
132            Self::Success => write!(f, "success"),
133            Self::Failed(e) => write!(f, "failed: {e}"),
134        }
135    }
136}
137
138/// Shared fields for all outcome types.
139#[derive(Debug, Clone)]
140pub struct Outcome {
141    pub program_name: String,
142    pub function: String,
143    pub output: Value,
144}
145
146impl Outcome {
147    pub fn output(&self) -> Value {
148        self.output.clone()
149    }
150}
151
152/// Outcome of an evaluation-only run (no execution trace, no verification).
153#[derive(Debug, Clone)]
154pub struct EvaluationOutcome {
155    pub outcome: Outcome,
156    pub status: EvaluationStatus,
157}
158
159impl EvaluationOutcome {
160    pub fn output(&self) -> Value {
161        self.outcome.output()
162    }
163}
164
165/// Outcome that includes execution and verification details.
166#[derive(Debug, Clone)]
167pub struct ExecutionOutcome {
168    pub outcome: Outcome,
169    pub verified: bool,
170    pub execution: String,
171    pub status: ExecutionStatus,
172}
173
174impl ExecutionOutcome {
175    pub fn output(&self) -> Value {
176        self.outcome.output()
177    }
178}
179
180/// Placeholder verifying key used for proof-less deployments.
181pub const PLACEHOLDER_VK: &str = "verifier1q9qqqqqqqqqqqqyvxgqqqqqqqqq87vsqqqqqqqqqhe7sqqqqqqqqqma4qqqqqqqqqq65yqqqqqqqqqqvqqqqqqqqqqqgtlaj49fmrk2d8slmselaj9tpucgxv6awu6yu4pfcn5xa0yy0tpxpc8wemasjvvxr9248vt3509vpk3u60ejyfd9xtvjmudpp7ljq2csk4yqz70ug3x8xp3xn3ul0yrrw0mvd2g8ju7rts50u3smue03gp99j88f0ky8h6fjlpvh58rmxv53mldmgrxa3fq6spsh8gt5whvsyu2rk4a2wmeyrgvvdf29pwp02srktxnvht3k6ff094usjtllggva2ym75xc4lzuqu9xx8ylfkm3qc7lf7ktk9uu9du5raukh828dzgq26hrarq5ajjl7pz7zk924kekjrp92r6jh9dpp05mxtuffwlmvew84dvnqrkre7lw29mkdzgdxwe7q8z0vnkv2vwwdraekw2va3plu7rkxhtnkuxvce0qkgxcxn5mtg9q2c3vxdf2r7jjse2g68dgvyh85q4mzfnvn07lletrpty3vypus00gfu9m47rzay4mh5w9f03z9zgzgzhkv0mupdqsk8naljqm9tc2qqzhf6yp3mnv2ey89xk7sw9pslzzlkndfd2upzmew4e4vnrkr556kexs9qrykkuhsr260mnrgh7uv0sp2meky0keeukaxgjdsnmy77kl48g3swcvqdjm50ejzr7x04vy7hn7anhd0xeetclxunnl7pd6e52qxdlr3nmutz4zr8f2xqa57a2zkl59a28w842cj4783zpy9hxw03k6vz4a3uu7sm072uqknpxjk8fyq4vxtqd08kd93c2mt40lj9ag35nm4rwcfjayejk57m9qqu83qnkrj3sz90pw808srmf705n2yu6gvqazpvu2mwm8x6mgtlsntxfhr0qas43rqxnccft36z4ygty86390t7vrt08derz8368z8ekn3yywxgp4uq24gm6e58tpp0lcvtpsm3nkwpnmzztx4qvkaf6vk38wg787h8mfpqqqqqqqqqqffkful";
182
183/// Placeholder certificate used for proof-less deployments.
184pub const PLACEHOLDER_CERT: &str =
185    "certificate1qyqsqqqqqqqqqqxvwszp09v860w62s2l4g6eqf0kzppyax5we36957ywqm2dplzwvvlqg0kwlnmhzfatnax7uaqt7yqqqw0sc4u";
186
187/// Deploy a program without generating certificates or proofs.
188fn deploy_without_proof(
189    vm: &VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
190    private_key: &PrivateKey<CurrentNetwork>,
191    program: &ProgramCore<CurrentNetwork>,
192    edition: u16,
193    consensus_version: ConsensusVersion,
194    rng: &mut ChaCha20Rng,
195) -> anyhow::Result<Transaction<CurrentNetwork>> {
196    // Create placeholder verifying keys and certificates for each function and record.
197    // The ledger requires exactly num_functions + num_records verifying keys per deployment.
198    let placeholder_vk = VerifyingKey::from_str(PLACEHOLDER_VK)?;
199    let placeholder_cert = Certificate::from_str(PLACEHOLDER_CERT)?;
200    let verifying_keys = program
201        .functions()
202        .keys()
203        .chain(program.records().keys())
204        .map(|name| (*name, (placeholder_vk.clone(), placeholder_cert.clone())))
205        .collect::<Vec<_>>();
206
207    // Create the deployment with placeholders.
208    let mut deployment = Deployment::new(edition, program.clone(), verifying_keys, None, None)
209        .map_err(|e| anyhow!("Failed to create deployment: {e}"))?;
210
211    // Set the program owner and checksum.
212    deployment.set_program_owner_raw(Some(Address::try_from(private_key)?));
213    deployment.set_program_checksum_raw(Some(deployment.program().to_checksum()));
214
215    // Compute the deployment ID and construct the owner.
216    let deployment_id = deployment.to_deployment_id()?;
217    let owner = ProgramOwner::new(private_key, deployment_id, rng)?;
218
219    // Calculate the minimum deployment cost.
220    let (minimum_deployment_cost, _) = deployment_cost(&vm.process().read(), &deployment, consensus_version)?;
221
222    // Authorize the fee (public, no proof).
223    let fee_authorization = vm.authorize_fee_public(private_key, minimum_deployment_cost, 0, deployment_id, rng)?;
224
225    // Create a fee transition without a proof.
226    let state_root = vm.block_store().current_state_root();
227    let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?;
228
229    Transaction::from_deployment(owner, deployment, fee).map_err(|e| anyhow!("Failed to create deployment tx: {e}"))
230}
231
232/// Execute a transition without generating proofs. Returns (Transaction, Response).
233fn execute_without_proof(
234    vm: &VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
235    private_key: &PrivateKey<CurrentNetwork>,
236    program_id: &str,
237    function_name: &str,
238    inputs: impl ExactSizeIterator<Item = impl TryInto<SvmValue<CurrentNetwork>>>,
239    consensus_version: ConsensusVersion,
240    rng: &mut ChaCha20Rng,
241) -> anyhow::Result<(Transaction<CurrentNetwork>, snarkvm::prelude::Response<CurrentNetwork>)> {
242    // Authorize the execution (fast, no proving).
243    let authorization = vm.authorize(private_key, program_id, function_name, inputs, rng)?;
244
245    // Evaluate to get the response (outputs, no proving).
246    let response = vm.process().read().evaluate::<AleoTestnetV0>(authorization.clone())?;
247
248    // Build the execution without a proof.
249    let state_root = vm.block_store().current_state_root();
250    let execution = Execution::from(authorization.transitions().values().cloned(), state_root, None)?;
251
252    // Calculate the execution cost for fee authorization.
253    let (cost, _) = execution_cost(&vm.process().read(), &execution, consensus_version)?;
254
255    // Authorize and create the fee without a proof.
256    let execution_id = authorization.to_execution_id()?;
257    let fee_authorization = vm.authorize_fee_public(private_key, cost, 0, execution_id, rng)?;
258    let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?;
259
260    let transaction = Transaction::from_execution(execution, Some(fee))?;
261    Ok((transaction, response))
262}
263
264/// Evaluates a set of cases against some programs without using a ledger.
265///
266/// Each case is run in isolation, producing an `EvaluationOutcome` for its
267/// output and success/failure status. Panics and errors in authorization or
268/// evaluation are caught and reported as failures.
269pub fn run_without_ledger(config: &Config, cases: &[Case]) -> Result<Vec<EvaluationOutcome>> {
270    // Nothing to do
271    if cases.is_empty() {
272        return Ok(Vec::new());
273    }
274
275    let programs_and_editions: Vec<(snarkvm::prelude::Program<CurrentNetwork>, u16)> = config
276        .programs
277        .iter()
278        .map(|Program { bytecode, name }| {
279            let program = snarkvm::prelude::Program::<CurrentNetwork>::from_str(bytecode)
280                .map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
281            // Assume edition 1. We can consider parametrizing this in the future.
282            let edition: u16 = 1;
283            Ok((program, edition))
284        })
285        .collect::<Result<Vec<_>>>()?;
286
287    let outcomes: Vec<EvaluationOutcome> = cases
288        .iter()
289        .map(|case| {
290            let rng = &mut ChaCha20Rng::seed_from_u64(config.seed);
291
292            // Helper to produce an EvaluationOutcome with `Failed` status
293            let failed_outcome = |e: String| EvaluationOutcome {
294                outcome: Outcome {
295                    program_name: case.program_name.clone(),
296                    function: case.function.clone(),
297                    output: Value::make_unit(),
298                },
299                status: EvaluationStatus::Failed(e),
300            };
301
302            let vm = match ConsensusStore::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::open(
303                StorageMode::Production,
304            ) {
305                Ok(store) => match VM::from(store) {
306                    Ok(vm) => vm,
307                    Err(e) => return failed_outcome(format!("VM init error: {e}")),
308                },
309                Err(e) => return failed_outcome(format!("Consensus store open error: {e}")),
310            };
311
312            if let Err(e) = vm.process().write().add_programs_with_editions(&programs_and_editions) {
313                return failed_outcome(format!("Failed to add programs: {e}"));
314            }
315
316            let private_key = match PrivateKey::from_str(leo_ast::TEST_PRIVATE_KEY) {
317                Ok(pk) => pk,
318                Err(e) => return failed_outcome(format!("Private key parse error: {e}")),
319            };
320            let program_id = match ProgramID::<CurrentNetwork>::from_str(&case.program_name) {
321                Ok(pid) => pid,
322                Err(e) => return failed_outcome(format!("ProgramID parse error: {e}")),
323            };
324            let function_id = match Identifier::<CurrentNetwork>::from_str(&case.function) {
325                Ok(fid) => fid,
326                Err(e) => return failed_outcome(format!("FunctionID parse error: {e}")),
327            };
328            let inputs = case.input.iter();
329
330            // --- catch panics from authorize ---
331            let authorization = match catch_unwind(AssertUnwindSafe(|| {
332                vm.authorize(&private_key, program_id, function_id, inputs, rng)
333            })) {
334                Ok(Ok(auth)) => auth,
335                Ok(Err(e)) => return failed_outcome(format!("{e}")),
336                Err(e) => return failed_outcome(format!("{e:?}")),
337            };
338
339            // --- catch panics from evaluate ---
340            let response =
341                match catch_unwind(AssertUnwindSafe(|| vm.process().read().evaluate::<AleoTestnetV0>(authorization))) {
342                    Ok(Ok(resp)) => resp,
343                    Ok(Err(e)) => return failed_outcome(format!("{e}")),
344                    Err(e) => return failed_outcome(format!("{e:?}")),
345                };
346
347            let outputs = response.outputs();
348            let output = match outputs.len() {
349                0 => Value::make_unit(),
350                1 => outputs[0].clone().into(),
351                _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
352            };
353
354            EvaluationOutcome {
355                outcome: Outcome { program_name: case.program_name.clone(), function: case.function.clone(), output },
356                status: EvaluationStatus::Success,
357            }
358        })
359        .collect();
360
361    Ok(outcomes)
362}
363
364/// Run the functions indicated by `cases` from the programs in `config`.
365pub fn run_with_ledger(config: &Config, case_sets: &[Vec<Case>]) -> Result<Vec<Vec<ExecutionOutcome>>> {
366    if case_sets.is_empty() {
367        return Ok(Vec::new());
368    }
369
370    // Initialize an rng.
371    let mut rng = ChaCha20Rng::seed_from_u64(config.seed);
372
373    // Initialize a genesis private key.
374    let genesis_private_key = PrivateKey::from_str(TEST_PRIVATE_KEY).unwrap();
375
376    // Store all of the non-genesis blocks created during set up.
377    let mut blocks = Vec::new();
378
379    // Load the genesis block.
380    let genesis_block =
381        Block::from_bytes_le(include_bytes!("resources/genesis_8d710d7e2_40val_snarkos_dev_network.bin"))?;
382
383    // Initialize a `Ledger`. This should always succeed.
384    let ledger =
385        Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(genesis_block.clone(), StorageMode::Production)
386            .unwrap();
387
388    // Advance the `VM` to the start height, defaulting to the height for the latest consensus version.
389    let latest_consensus_version = ConsensusVersion::latest();
390    let start_height =
391        config.start_height.unwrap_or(CurrentNetwork::CONSENSUS_HEIGHT(latest_consensus_version).unwrap());
392    while ledger.latest_height() < start_height {
393        let block = ledger
394            .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![], &mut rng)
395            .map_err(|_| anyhow!("Failed to prepare advance to next beacon block"))?;
396        ledger.advance_to_next_block(&block).map_err(|_| anyhow!("Failed to advance to next block"))?;
397        blocks.push(block);
398    }
399
400    // Deploy each bytecode separately.
401    for Program { bytecode, name } in &config.programs {
402        // Parse the bytecode as an Aleo program.
403        // Note that this function checks that the bytecode is well-formed.
404        let aleo_program =
405            ProgramCore::from_str(bytecode).map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
406
407        let mut deploy = |edition: u16| -> Result<()> {
408            let deployment = if config.skip_proving {
409                deploy_without_proof(
410                    ledger.vm(),
411                    &genesis_private_key,
412                    &aleo_program,
413                    edition,
414                    latest_consensus_version,
415                    &mut rng,
416                )
417                .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?
418            } else {
419                // Add the program to the ledger.
420                // Note that this function performs an additional validity check on the bytecode.
421                ledger
422                    .vm()
423                    .deploy(&genesis_private_key, &aleo_program, None, 0, None, &mut rng)
424                    .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?
425            };
426            let block = ledger
427                .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![deployment], &mut rng)
428                .map_err(|e| anyhow!("Failed to prepare to advance block for program {name}: {e}"))?;
429            ledger
430                .advance_to_next_block(&block)
431                .map_err(|e| anyhow!("Failed to advance block for program {name}: {e}"))?;
432
433            // Check that the deployment transaction was accepted.
434            if block.transactions().num_accepted() != 1 {
435                return Err(anyhow!("Deployment transaction for program {name} not accepted.").into());
436            }
437
438            // Store the block.
439            blocks.push(block);
440
441            Ok(())
442        };
443
444        // Deploy the program.
445        deploy(0)?;
446        // If the program does not have a constructor, deploy it twice to satisfy the edition requirement.
447        if !aleo_program.contains_constructor() {
448            deploy(1)?;
449        }
450    }
451
452    // Initialize ledger instances for each case set.
453    let mut indexed_ledgers = vec![(0, ledger)];
454    indexed_ledgers.extend(
455        (1..case_sets.len())
456            .map(|i| {
457                // Initialize a `Ledger`. This should always succeed.
458                let l = Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(
459                    genesis_block.clone(),
460                    StorageMode::Production,
461                )
462                .expect("Failed to load copy of ledger");
463                // Add the setup blocks.
464                for block in blocks.iter() {
465                    l.advance_to_next_block(block).expect("Failed to add setup block to ledger");
466                }
467
468                (i, l)
469            })
470            .collect::<Vec<_>>(),
471    );
472
473    // For each of the case sets, run the cases sequentially.
474    let results = indexed_ledgers
475        .into_iter()
476        .map(|(index, ledger)| {
477            // Get the cases for this ledger.
478            let cases = &case_sets[index];
479            // Clone the RNG.
480            let mut rng = rng.clone();
481
482            // Fund each private key used in the test cases with 1M ALEO.
483            let skip_proving = config.skip_proving;
484            let transactions: Vec<Transaction<CurrentNetwork>> = cases
485                .iter()
486                .filter_map(|case| case.private_key.as_ref())
487                .map(|key| {
488                    // Parse the private key.
489                    let private_key =
490                        PrivateKey::<CurrentNetwork>::from_str(key).expect("Failed to parse private key.");
491                    // Convert the private key to an address.
492                    let address = Address::try_from(private_key).expect("Failed to convert private key to address.");
493                    // Generate the transaction.
494                    if skip_proving {
495                        let (tx, _) = execute_without_proof(
496                            ledger.vm(),
497                            &genesis_private_key,
498                            "credits.aleo",
499                            "transfer_public",
500                            [
501                                SvmValue::from_str(&format!("{address}")).expect("Failed to parse recipient address"),
502                                SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
503                            ]
504                            .iter(),
505                            latest_consensus_version,
506                            &mut rng,
507                        )
508                        .expect("Failed to generate funding transaction");
509                        tx
510                    } else {
511                        ledger
512                            .vm()
513                            .execute(
514                                &genesis_private_key,
515                                ("credits.aleo", "transfer_public"),
516                                [
517                                    SvmValue::from_str(&format!("{address}"))
518                                        .expect("Failed to parse recipient address"),
519                                    SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
520                                ]
521                                .iter(),
522                                None,
523                                0u64,
524                                None,
525                                &mut rng,
526                            )
527                            .expect("Failed to generate funding transaction")
528                    }
529                })
530                .collect();
531
532            // Create a block with the funding transactions.
533            let block = ledger
534                .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], transactions, &mut rng)
535                .expect("Failed to prepare advance to next beacon block");
536            // Assert that no transactions were aborted or rejected.
537            assert!(block.aborted_transaction_ids().is_empty());
538            assert_eq!(block.transactions().num_rejected(), 0);
539            // Advance the ledger to the next block.
540            ledger.advance_to_next_block(&block).expect("Failed to advance to next block");
541
542            let mut case_outcomes = Vec::new();
543
544            for case in cases {
545                assert!(
546                    ledger.vm().contains_program(&ProgramID::from_str(&case.program_name).unwrap()),
547                    "Program {} should exist.",
548                    case.program_name
549                );
550
551                let private_key = case
552                    .private_key
553                    .as_ref()
554                    .map(|key| PrivateKey::from_str(key).expect("Failed to parse private key."))
555                    .unwrap_or(genesis_private_key);
556
557                let mut execution = None;
558                let mut verified = false;
559                let mut status = ExecutionStatus::None;
560                let mut abort_reason: Option<String> = None;
561
562                // Halts are handled by panics, so we need to catch them.
563                // I'm not thrilled about this usage of `AssertUnwindSafe`, but it seems to be
564                // used frequently in SnarkVM anyway.
565                let execute_output = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
566                    if skip_proving {
567                        execute_without_proof(
568                            ledger.vm(),
569                            &private_key,
570                            &case.program_name,
571                            &case.function,
572                            case.input.iter(),
573                            latest_consensus_version,
574                            &mut rng,
575                        )
576                    } else {
577                        ledger
578                            .vm()
579                            .execute_with_response(
580                                &private_key,
581                                (&case.program_name, &case.function),
582                                case.input.iter(),
583                                None,
584                                0,
585                                None,
586                                &mut rng,
587                            )
588                            .map_err(anyhow::Error::from)
589                    }
590                }));
591
592                if let Err(payload) = execute_output {
593                    let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
594                    let s2 = payload.downcast_ref::<String>().cloned();
595                    let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
596
597                    case_outcomes.push(ExecutionOutcome {
598                        outcome: Outcome {
599                            program_name: case.program_name.clone(),
600                            function: case.function.clone(),
601                            output: Value::make_unit(),
602                        },
603                        status: ExecutionStatus::Halted(s),
604                        verified: false,
605                        execution: "".to_string(),
606                    });
607
608                    continue;
609                }
610
611                let result = execute_output.unwrap().and_then(|(transaction, response)| {
612                    // Skip verification when proving is skipped — proofs are absent
613                    // and verification is meaningless.
614                    verified = skip_proving || ledger.vm().check_transaction(&transaction, None, &mut rng).is_ok();
615                    execution = Some(transaction.clone());
616                    let block = ledger
617                        .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], &mut rng)
618                        .map_err(|e| anyhow::anyhow!("{e}"))?;
619                    status =
620                        match (block.aborted_transaction_ids().is_empty(), block.transactions().num_accepted() == 1) {
621                            (false, _) => {
622                                // Attempt check_transaction to diagnose abort reason.
623                                if let Some(ref tx) = execution
624                                    && let Err(e) = ledger.vm().check_transaction(tx, None, &mut rng)
625                                {
626                                    abort_reason = Some(format!("{e}"));
627                                }
628                                ExecutionStatus::Aborted(abort_reason.take())
629                            }
630                            (true, true) => ExecutionStatus::Accepted,
631                            (true, false) => ExecutionStatus::Rejected,
632                        };
633                    ledger.advance_to_next_block(&block)?;
634                    Ok(response)
635                });
636
637                let output = match result {
638                    Ok(response) => {
639                        let outputs = response.outputs();
640                        match outputs.len() {
641                            0 => Value::make_unit(),
642                            1 => outputs[0].clone().into(),
643                            _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
644                        }
645                    }
646                    Err(e) => Value::make_string(format!("Failed to extract output: {e}")),
647                };
648
649                // Extract the execution, removing the global state root and proof.
650                // This is necessary as they are not deterministic across runs, even with RNG fixed.
651                let execution = if let Some(Transaction::Execute(_, _, execution, _)) = execution {
652                    Some(Execution::from(execution.into_transitions(), Default::default(), None).unwrap())
653                } else {
654                    None
655                };
656
657                case_outcomes.push(ExecutionOutcome {
658                    outcome: Outcome {
659                        program_name: case.program_name.clone(),
660                        function: case.function.clone(),
661                        output,
662                    },
663                    status,
664                    verified,
665                    execution: serde_json::to_string_pretty(&execution).expect("Serialization failure"),
666                });
667            }
668
669            Ok((index, case_outcomes))
670        })
671        .collect::<Result<Vec<_>>>()?;
672
673    // Reorder results to match input order.
674    let mut ordered_results: Vec<Vec<ExecutionOutcome>> = vec![Default::default(); case_sets.len()];
675    for (index, outcomes) in results.into_iter() {
676        ordered_results[index] = outcomes;
677    }
678
679    Ok(ordered_results)
680}