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 rustyline::{Editor, config::Configurer, error::ReadlineError};
10use std::{ops::ControlFlow, path::PathBuf};
11use yansi::Paint;
12
13pub fn run() -> Result<()> {
15 foundry_cli::opts::GlobalArgs::check_introspect::<Chisel>();
18 foundry_cli::opts::GlobalArgs::check_markdown_help::<Chisel>();
19
20 setup()?;
21
22 let args = Chisel::parse();
23 args.global.init()?;
24 args.global.tokio_runtime().block_on(run_command(args))
25}
26
27pub fn setup() -> Result<()> {
29 utils::common_setup();
30 utils::subscriber();
31
32 Ok(())
33}
34
35macro_rules! try_cf {
36 ($e:expr) => {
37 match $e {
38 ControlFlow::Continue(()) => {}
39 ControlFlow::Break(()) => return Ok(()),
40 }
41 };
42}
43
44pub async fn run_command(args: Chisel) -> Result<()> {
46 let (config, evm_opts) = args.load_config_and_evm_opts()?;
48
49 let mut dispatcher = ChiselDispatcher::new(crate::source::SessionSourceConfig {
51 traces: config.verbosity > 0,
53 foundry_config: config,
54 no_vm: args.no_vm,
55 evm_opts,
56 backend: None,
57 calldata: None,
58 ir_minimum: args.ir_minimum,
59 })?;
60
61 evaluate_prelude(&mut dispatcher, args.prelude).await?;
63
64 if let Some(cmd) = args.cmd {
65 try_cf!(handle_cli_command(&mut dispatcher, cmd).await?);
66 return Ok(());
67 }
68
69 let mut rl = Editor::<SolidityHelper, _>::new()?;
70 rl.set_helper(Some(dispatcher.helper.clone()));
71 rl.set_auto_add_history(true);
72 if let Some(path) = chisel_history_file() {
73 let _ = rl.load_history(&path);
74 }
75
76 sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?;
77
78 let mut interrupt = false;
80 loop {
81 match rl.readline(&dispatcher.get_prompt()) {
82 Ok(line) => {
83 debug!("dispatching next line: {line}");
84 interrupt = false;
86
87 let r = dispatcher.dispatch(&line).await;
89 dispatcher.helper.set_errored(r.is_err());
90 match r {
91 Ok(ControlFlow::Continue(())) => {}
92 Ok(ControlFlow::Break(())) => break,
93 Err(e) => {
94 sh_err!("{}", foundry_common::errors::display_chain(&e))?;
95 }
96 }
97 }
98 Err(ReadlineError::Interrupted) => {
99 if interrupt {
100 break;
101 }
102 sh_println!("(To exit, press Ctrl+C again)")?;
103 interrupt = true;
104 }
105 Err(ReadlineError::Eof) => break,
106 Err(err) => {
107 sh_err!("{err}")?;
108 break;
109 }
110 }
111 }
112
113 if let Some(path) = chisel_history_file() {
114 let _ = rl.save_history(&path);
115 }
116
117 Ok(())
118}
119
120async fn evaluate_prelude(
123 dispatcher: &mut ChiselDispatcher,
124 maybe_prelude: Option<PathBuf>,
125) -> Result<()> {
126 let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
127 if prelude_dir.is_file() {
128 sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
129 try_cf!(load_prelude_file(dispatcher, prelude_dir).await?);
130 sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
131 } else {
132 let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
133 let mut print_success_msg = false;
134 for source_file in prelude_sources {
135 print_success_msg = true;
136 sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
137 try_cf!(load_prelude_file(dispatcher, source_file).await?);
138 }
139
140 if print_success_msg {
141 sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
142 }
143 }
144 Ok(())
145}
146
147async fn load_prelude_file(
149 dispatcher: &mut ChiselDispatcher,
150 file: PathBuf,
151) -> Result<ControlFlow<()>> {
152 let prelude = fs::read_to_string(file)
153 .wrap_err("Could not load source file. Are you sure this path is correct?")?;
154 dispatcher.dispatch(&prelude).await
155}
156
157async fn handle_cli_command(
158 d: &mut ChiselDispatcher,
159 cmd: ChiselSubcommand,
160) -> Result<ControlFlow<()>> {
161 match cmd {
162 ChiselSubcommand::List => d.dispatch_command(ChiselCommand::ListSessions).await,
163 ChiselSubcommand::Load { id } => d.dispatch_command(ChiselCommand::Load { id }).await,
164 ChiselSubcommand::View { id } => {
165 let ControlFlow::Continue(()) = d.dispatch_command(ChiselCommand::Load { id }).await?
166 else {
167 return Ok(ControlFlow::Break(()));
168 };
169 d.dispatch_command(ChiselCommand::Source).await
170 }
171 ChiselSubcommand::ClearCache => d.dispatch_command(ChiselCommand::ClearCache).await,
172 ChiselSubcommand::Eval { command } => d.dispatch(&command).await,
173 }
174}
175
176fn chisel_history_file() -> Option<PathBuf> {
177 foundry_config::Config::foundry_dir().map(|p| p.join(".chisel_history"))
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use clap::CommandFactory;
184
185 #[test]
186 fn verify_cli() {
187 Chisel::command().debug_assert();
188 }
189
190 #[test]
192 fn introspect_command_ids_are_unique() {
193 use foundry_cli::introspect::{CommandRegistry, build_document, duplicate_command_ids};
194 let cmd = Chisel::command();
195 let doc = build_document(&cmd, &CommandRegistry::EMPTY);
196 let dups = duplicate_command_ids(&doc);
197 assert!(dups.is_empty(), "duplicate chisel command_ids: {dups:?}");
198 }
199
200 #[test]
203 fn introspect_document_is_valid_json() {
204 use foundry_cli::introspect::{
205 CommandRegistry, INTROSPECT_SCHEMA_ID, IntrospectDocument, render_introspect_document,
206 };
207 let cmd = Chisel::command();
208 let json = render_introspect_document(&cmd, &CommandRegistry::EMPTY);
209 let doc: IntrospectDocument = serde_json::from_str(&json).expect("valid JSON");
210 assert_eq!(doc.schema_id, INTROSPECT_SCHEMA_ID);
211 assert_eq!(doc.binary.name, "chisel");
212 }
213}