1use crate::{
2 opts::{Chisel, ChiselSubcommand},
3 prelude::{ChiselCommand, ChiselDispatcher, SolidityHelper},
4};
5use eyre::{Context, Result};
6use foundry_cli::utils::{self, LoadConfig};
7use foundry_common::fs;
8use foundry_config::Config;
9#[cfg(feature = "optimism")]
10use foundry_evm::core::evm::OpEvmNetwork;
11use foundry_evm::{
12 core::evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork},
13 opts::EvmOpts,
14};
15use rustyline::{Editor, config::Configurer, error::ReadlineError};
16use std::{ops::ControlFlow, path::PathBuf};
17use yansi::Paint;
18
19pub fn run() -> Result<()> {
21 foundry_cli::machine::check_machine();
24 foundry_cli::opts::GlobalArgs::check_introspect::<Chisel>();
25 foundry_cli::opts::GlobalArgs::check_markdown_help::<Chisel>();
26
27 setup()?;
28
29 let args = foundry_cli::parse_or_exit::<Chisel>();
30 args.global.init()?;
31 args.global.tokio_runtime().block_on(run_command(args))
32}
33
34pub fn setup() -> Result<()> {
36 utils::common_setup();
37 utils::subscriber();
38
39 Ok(())
40}
41
42macro_rules! try_cf {
43 ($e:expr) => {
44 match $e {
45 ControlFlow::Continue(()) => {}
46 ControlFlow::Break(()) => return Ok(()),
47 }
48 };
49}
50
51pub async fn run_command(args: Chisel) -> Result<()> {
53 let (mut config, mut evm_opts) = args.load_config_and_evm_opts()?;
55
56 if let Some(chain) = config.chain {
57 evm_opts.networks = evm_opts.networks.with_chain_id(chain.id());
58 }
59 evm_opts.infer_network_from_fork().await;
60 config.networks = evm_opts.networks;
61
62 if evm_opts.networks.is_tempo() {
63 return run_command_with_network::<TempoEvmNetwork>(args, config, evm_opts).await;
64 }
65
66 #[cfg(feature = "optimism")]
67 if evm_opts.networks.is_optimism() {
68 return run_command_with_network::<OpEvmNetwork>(args, config, evm_opts).await;
69 }
70
71 run_command_with_network::<EthEvmNetwork>(args, config, evm_opts).await
72}
73
74async fn run_command_with_network<FEN: FoundryEvmNetwork>(
75 args: Chisel,
76 config: Config,
77 evm_opts: EvmOpts,
78) -> Result<()> {
79 let mut dispatcher = ChiselDispatcher::<FEN>::new(crate::source::SessionSourceConfig {
81 traces: config.verbosity > 0,
83 foundry_config: config,
84 no_vm: args.no_vm,
85 evm_opts,
86 backend: None,
87 calldata: None,
88 ir_minimum: args.ir_minimum,
89 })?;
90
91 evaluate_prelude(&mut dispatcher, args.prelude).await?;
93
94 if let Some(cmd) = args.cmd {
95 try_cf!(handle_cli_command(&mut dispatcher, cmd).await?);
96 return Ok(());
97 }
98
99 let mut rl = Editor::<SolidityHelper, _>::new()?;
100 rl.set_helper(Some(dispatcher.helper.clone()));
101 rl.set_auto_add_history(true);
102 if let Some(path) = chisel_history_file() {
103 let _ = rl.load_history(&path);
104 }
105
106 sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?;
107
108 let mut interrupt = false;
110 loop {
111 match rl.readline(&dispatcher.get_prompt()) {
112 Ok(line) => {
113 debug!("dispatching next line: {line}");
114 interrupt = false;
116
117 let r = dispatcher.dispatch(&line).await;
119 dispatcher.helper.set_errored(r.is_err());
120 match r {
121 Ok(ControlFlow::Continue(())) => {}
122 Ok(ControlFlow::Break(())) => break,
123 Err(e) => {
124 sh_err!("{}", foundry_common::errors::display_chain(&e))?;
125 }
126 }
127 }
128 Err(ReadlineError::Interrupted) => {
129 if interrupt {
130 break;
131 }
132 sh_println!("(To exit, press Ctrl+C again)")?;
133 interrupt = true;
134 }
135 Err(ReadlineError::Eof) => break,
136 Err(err) => {
137 sh_err!("{err}")?;
138 break;
139 }
140 }
141 }
142
143 if let Some(path) = chisel_history_file() {
144 let _ = rl.save_history(&path);
145 }
146
147 Ok(())
148}
149
150async fn evaluate_prelude<FEN: FoundryEvmNetwork>(
153 dispatcher: &mut ChiselDispatcher<FEN>,
154 maybe_prelude: Option<PathBuf>,
155) -> Result<()> {
156 let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
157 if prelude_dir.is_file() {
158 sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
159 try_cf!(load_prelude_file(dispatcher, prelude_dir).await?);
160 sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
161 } else {
162 let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
163 let mut print_success_msg = false;
164 for source_file in prelude_sources {
165 print_success_msg = true;
166 sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
167 try_cf!(load_prelude_file(dispatcher, source_file).await?);
168 }
169
170 if print_success_msg {
171 sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
172 }
173 }
174 Ok(())
175}
176
177async fn load_prelude_file<FEN: FoundryEvmNetwork>(
179 dispatcher: &mut ChiselDispatcher<FEN>,
180 file: PathBuf,
181) -> Result<ControlFlow<()>> {
182 let prelude = fs::read_to_string(file)
183 .wrap_err("Could not load source file. Are you sure this path is correct?")?;
184 dispatcher.dispatch(&prelude).await
185}
186
187async fn handle_cli_command<FEN: FoundryEvmNetwork>(
188 d: &mut ChiselDispatcher<FEN>,
189 cmd: ChiselSubcommand,
190) -> Result<ControlFlow<()>> {
191 match cmd {
192 ChiselSubcommand::List => d.dispatch_command(ChiselCommand::ListSessions).await,
193 ChiselSubcommand::Load { id } => d.dispatch_command(ChiselCommand::Load { id }).await,
194 ChiselSubcommand::View { id } => {
195 let ControlFlow::Continue(()) = d.dispatch_command(ChiselCommand::Load { id }).await?
196 else {
197 return Ok(ControlFlow::Break(()));
198 };
199 d.dispatch_command(ChiselCommand::Source).await
200 }
201 ChiselSubcommand::ClearCache => d.dispatch_command(ChiselCommand::ClearCache).await,
202 ChiselSubcommand::Eval { command } => d.dispatch(&command).await,
203 }
204}
205
206fn chisel_history_file() -> Option<PathBuf> {
207 foundry_config::Config::foundry_dir().map(|p| p.join(".chisel_history"))
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use clap::CommandFactory;
214
215 #[test]
216 fn verify_cli() {
217 Chisel::command().debug_assert();
218 }
219
220 #[test]
222 fn introspect_command_ids_are_unique() {
223 use foundry_cli::introspect::{CommandRegistry, build_document, duplicate_command_ids};
224 let cmd = Chisel::command();
225 let doc = build_document(&cmd, &CommandRegistry::EMPTY);
226 let dups = duplicate_command_ids(&doc);
227 assert!(dups.is_empty(), "duplicate chisel command_ids: {dups:?}");
228 }
229
230 #[test]
233 fn introspect_document_is_valid_json() {
234 use foundry_cli::introspect::{
235 CommandRegistry, INTROSPECT_SCHEMA_ID, IntrospectDocument, render_introspect_document,
236 };
237 let cmd = Chisel::command();
238 let json = render_introspect_document(&cmd, &CommandRegistry::EMPTY);
239 let doc: IntrospectDocument = serde_json::from_str(&json).expect("valid JSON");
240 assert_eq!(doc.schema_id, INTROSPECT_SCHEMA_ID);
241 assert_eq!(doc.binary.name, "chisel");
242 }
243
244 #[test]
246 fn introspect_capabilities_are_consistent() {
247 use foundry_cli::introspect::{CommandRegistry, build_document, capability_violations};
248 let cmd = Chisel::command();
249 let doc = build_document(&cmd, &CommandRegistry::EMPTY);
250 let v = capability_violations(&doc);
251 assert!(v.is_empty(), "chisel capability violations: {v:?}");
252 }
253}