Skip to main content

chisel/
args.rs

1use crate::{
2    opts::{Chisel, ChiselSubcommand},
3    prelude::{ChiselCommand, ChiselDispatcher, SolidityHelper},
4};
5use clap::Parser;
6use eyre::{Context, Result};
7use foundry_cli::utils::{self, LoadConfig};
8use foundry_common::fs;
9use foundry_config::Config;
10#[cfg(feature = "optimism")]
11use foundry_evm::core::evm::OpEvmNetwork;
12use foundry_evm::{
13    core::evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork},
14    opts::EvmOpts,
15};
16use rustyline::{Editor, config::Configurer, error::ReadlineError};
17use std::{ops::ControlFlow, path::PathBuf};
18use yansi::Paint;
19
20/// Run the `chisel` command line interface.
21pub fn run() -> Result<()> {
22    foundry_cli::opts::GlobalArgs::check_markdown_help::<Chisel>();
23
24    setup()?;
25
26    let args = Chisel::parse();
27    args.global.init()?;
28    args.global.tokio_runtime().block_on(run_command(args))
29}
30
31/// Setup the global logger and other utilities.
32pub fn setup() -> Result<()> {
33    utils::common_setup();
34    utils::subscriber();
35
36    Ok(())
37}
38
39macro_rules! try_cf {
40    ($e:expr) => {
41        match $e {
42            ControlFlow::Continue(()) => {}
43            ControlFlow::Break(()) => return Ok(()),
44        }
45    };
46}
47
48/// Run the subcommand.
49pub async fn run_command(args: Chisel) -> Result<()> {
50    // Load configuration
51    let (mut config, mut evm_opts) = args.load_config_and_evm_opts()?;
52
53    if let Some(chain) = config.chain {
54        evm_opts.networks = evm_opts.networks.with_chain_id(chain.id());
55    }
56    evm_opts.infer_network_from_fork().await;
57    config.networks = evm_opts.networks;
58
59    if evm_opts.networks.is_tempo() {
60        return run_command_with_network::<TempoEvmNetwork>(args, config, evm_opts).await;
61    }
62
63    #[cfg(feature = "optimism")]
64    if evm_opts.networks.is_optimism() {
65        return run_command_with_network::<OpEvmNetwork>(args, config, evm_opts).await;
66    }
67
68    run_command_with_network::<EthEvmNetwork>(args, config, evm_opts).await
69}
70
71async fn run_command_with_network<FEN: FoundryEvmNetwork>(
72    args: Chisel,
73    config: Config,
74    evm_opts: EvmOpts,
75) -> Result<()> {
76    // Create a new cli dispatcher
77    let mut dispatcher = ChiselDispatcher::<FEN>::new(crate::source::SessionSourceConfig {
78        // Enable traces if any level of verbosity was passed
79        traces: config.verbosity > 0,
80        foundry_config: config,
81        no_vm: args.no_vm,
82        evm_opts,
83        backend: None,
84        calldata: None,
85        ir_minimum: args.ir_minimum,
86    })?;
87
88    // Execute prelude Solidity source files
89    evaluate_prelude(&mut dispatcher, args.prelude).await?;
90
91    if let Some(cmd) = args.cmd {
92        try_cf!(handle_cli_command(&mut dispatcher, cmd).await?);
93        return Ok(());
94    }
95
96    let mut rl = Editor::<SolidityHelper, _>::new()?;
97    rl.set_helper(Some(dispatcher.helper.clone()));
98    rl.set_auto_add_history(true);
99    if let Some(path) = chisel_history_file() {
100        let _ = rl.load_history(&path);
101    }
102
103    sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?;
104
105    // REPL loop.
106    let mut interrupt = false;
107    loop {
108        let prompt = dispatcher.get_prompt();
109        match rl.readline(prompt.as_ref()) {
110            Ok(line) => {
111                debug!("dispatching next line: {line}");
112                // Clear interrupt flag.
113                interrupt = false;
114
115                // Dispatch and match results.
116                let r = dispatcher.dispatch(&line).await;
117                dispatcher.helper.set_errored(r.is_err());
118                match r {
119                    Ok(ControlFlow::Continue(())) => {}
120                    Ok(ControlFlow::Break(())) => break,
121                    Err(e) => {
122                        sh_err!("{}", foundry_common::errors::display_chain(&e))?;
123                    }
124                }
125            }
126            Err(ReadlineError::Interrupted) => {
127                if interrupt {
128                    break;
129                }
130                sh_println!("(To exit, press Ctrl+C again)")?;
131                interrupt = true;
132            }
133            Err(ReadlineError::Eof) => break,
134            Err(err) => {
135                sh_err!("{err}")?;
136                break;
137            }
138        }
139    }
140
141    if let Some(path) = chisel_history_file() {
142        let _ = rl.save_history(&path);
143    }
144
145    Ok(())
146}
147
148/// Evaluate multiple Solidity source files contained within a
149/// Chisel prelude directory.
150async fn evaluate_prelude<FEN: FoundryEvmNetwork>(
151    dispatcher: &mut ChiselDispatcher<FEN>,
152    maybe_prelude: Option<PathBuf>,
153) -> Result<()> {
154    let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
155    if prelude_dir.is_file() {
156        sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
157        try_cf!(load_prelude_file(dispatcher, prelude_dir).await?);
158        sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
159    } else {
160        let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
161        let mut print_success_msg = false;
162        for source_file in prelude_sources {
163            print_success_msg = true;
164            sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
165            try_cf!(load_prelude_file(dispatcher, source_file).await?);
166        }
167
168        if print_success_msg {
169            sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
170        }
171    }
172    Ok(())
173}
174
175/// Loads a single Solidity file into the prelude.
176async fn load_prelude_file<FEN: FoundryEvmNetwork>(
177    dispatcher: &mut ChiselDispatcher<FEN>,
178    file: PathBuf,
179) -> Result<ControlFlow<()>> {
180    let prelude = fs::read_to_string(file)
181        .wrap_err("Could not load source file. Are you sure this path is correct?")?;
182    dispatcher.dispatch(&prelude).await
183}
184
185async fn handle_cli_command<FEN: FoundryEvmNetwork>(
186    d: &mut ChiselDispatcher<FEN>,
187    cmd: ChiselSubcommand,
188) -> Result<ControlFlow<()>> {
189    match cmd {
190        ChiselSubcommand::List => d.dispatch_command(ChiselCommand::ListSessions).await,
191        ChiselSubcommand::Load { id } => d.dispatch_command(ChiselCommand::Load { id }).await,
192        ChiselSubcommand::View { id } => {
193            let ControlFlow::Continue(()) = d.dispatch_command(ChiselCommand::Load { id }).await?
194            else {
195                return Ok(ControlFlow::Break(()));
196            };
197            d.dispatch_command(ChiselCommand::Source).await
198        }
199        ChiselSubcommand::ClearCache => d.dispatch_command(ChiselCommand::ClearCache).await,
200        ChiselSubcommand::Eval { command } => d.dispatch(&command).await,
201    }
202}
203
204fn chisel_history_file() -> Option<PathBuf> {
205    foundry_config::Config::foundry_dir().map(|p| p.join(".chisel_history"))
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use clap::CommandFactory;
212
213    #[test]
214    fn verify_cli() {
215        Chisel::command().debug_assert();
216    }
217}