leo_lang/cli/commands/
debug.rs

1// Copyright (C) 2019-2025 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
17use leo_package::{Package, ProgramData};
18use leo_span::Symbol;
19
20use snarkvm::prelude::TestnetV0;
21
22use indexmap::IndexSet;
23use std::path::PathBuf;
24
25use super::*;
26
27/// Debugs an Aleo program through the interpreter.
28#[derive(Parser, Debug)]
29pub struct LeoDebug {
30    #[arg(long, help = "Use these source files instead of finding source files through the project structure.", num_args = 1..)]
31    pub(crate) paths: Vec<String>,
32    #[arg(long, help = "The block height, accessible via block.height.", default_value = "0")]
33    pub(crate) block_height: u32,
34    #[arg(long, action, help = "Use the text user interface.")]
35    pub(crate) tui: bool,
36    #[clap(flatten)]
37    pub(crate) compiler_options: BuildOptions,
38    #[clap(flatten)]
39    pub(crate) env_override: EnvOptions,
40}
41
42impl Command for LeoDebug {
43    type Input = Option<Package>;
44    type Output = ();
45
46    fn log_span(&self) -> Span {
47        tracing::span!(tracing::Level::INFO, "Leo")
48    }
49
50    fn prelude(&self, context: Context) -> Result<Self::Input> {
51        if self.paths.is_empty() {
52            let package = LeoBuild { options: self.compiler_options.clone(), env_override: self.env_override.clone() }
53                .execute(context)?;
54            Ok(Some(package))
55        } else {
56            Ok(None)
57        }
58    }
59
60    fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
61        handle_debug(&self, context, input)
62    }
63}
64
65fn handle_debug(command: &LeoDebug, context: Context, package: Option<Package>) -> Result<()> {
66    if command.paths.is_empty() {
67        let package = package.unwrap();
68
69        // Get the private key.
70        let private_key = context.get_private_key(&None)?;
71        let address = Address::try_from(&private_key)?;
72
73        // Get the paths of all local dependencies.
74        let local_dependency_paths: Vec<PathBuf> = package
75            .programs
76            .iter()
77            .flat_map(|program| match &program.data {
78                ProgramData::SourcePath(path) => Some(path.clone()),
79                ProgramData::Bytecode(..) => None,
80            })
81            .collect();
82
83        let local_dependency_symbols: IndexSet<Symbol> = package
84            .programs
85            .iter()
86            .flat_map(|program| match &program.data {
87                ProgramData::SourcePath(..) => {
88                    // It's a local dependency.
89                    Some(program.name)
90                }
91                ProgramData::Bytecode(..) => {
92                    // It's a network dependency.
93                    None
94                }
95            })
96            .collect();
97
98        let imports_directory = package.imports_directory();
99
100        // Get the paths to .aleo files in `imports` - but filter out the ones corresponding to local dependencies.
101        let aleo_paths: Vec<PathBuf> = imports_directory
102            .read_dir()
103            .ok()
104            .into_iter()
105            .flatten()
106            .flat_map(|maybe_filename| maybe_filename.ok())
107            .filter(|entry| entry.file_type().ok().map(|filetype| filetype.is_file()).unwrap_or(false))
108            .flat_map(|entry| {
109                let path = entry.path();
110                if let Some(filename) = leo_package::filename_no_aleo_extension(&path) {
111                    let symbol = Symbol::intern(filename);
112                    if local_dependency_symbols.contains(&symbol) { None } else { Some(path) }
113                } else {
114                    None
115                }
116            })
117            .collect();
118
119        // No need to keep this around while the interpreter runs.
120        std::mem::drop(package);
121
122        leo_interpreter::interpret(&local_dependency_paths, &aleo_paths, address, command.block_height, command.tui)
123    } else {
124        let private_key: PrivateKey<TestnetV0> = PrivateKey::from_str(leo_package::TEST_PRIVATE_KEY)?;
125        let address = Address::try_from(&private_key)?;
126
127        let leo_paths: Vec<PathBuf> = command
128            .paths
129            .iter()
130            .filter(|path_str| path_str.ends_with(".leo"))
131            .map(|path_str| path_str.into())
132            .collect();
133        let aleo_paths: Vec<PathBuf> = command
134            .paths
135            .iter()
136            .filter(|path_str| !path_str.ends_with(".leo"))
137            .map(|path_str| path_str.into())
138            .collect();
139
140        leo_interpreter::interpret(&leo_paths, &aleo_paths, address, command.block_height, command.tui)
141    }
142}