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
20pub 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
31pub 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
48pub async fn run_command(args: Chisel) -> Result<()> {
50 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 let mut dispatcher = ChiselDispatcher::<FEN>::new(crate::source::SessionSourceConfig {
78 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 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 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 interrupt = false;
114
115 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
148async 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
175async 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}