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 foundry_evm_networks::NetworkConfigs;
17use rustyline::{Editor, config::Configurer, error::ReadlineError};
18use std::{ops::ControlFlow, path::PathBuf};
19use yansi::Paint;
20
21pub fn run() -> Result<()> {
23 foundry_cli::opts::GlobalArgs::check_markdown_help::<Chisel>();
24
25 setup()?;
26
27 let args = Chisel::parse();
28 args.global.init()?;
29 args.global.tokio_runtime().block_on(run_command(args))
30}
31
32pub fn setup() -> Result<()> {
34 utils::common_setup();
35 utils::subscriber();
36
37 Ok(())
38}
39
40macro_rules! try_cf {
41 ($e:expr) => {
42 match $e {
43 ControlFlow::Continue(()) => {}
44 ControlFlow::Break(()) => return Ok(()),
45 }
46 };
47}
48
49pub async fn run_command(args: Chisel) -> Result<()> {
51 let (mut config, mut evm_opts) = args.load_config_and_evm_opts()?;
53
54 evm_opts.networks =
55 infer_network_from_chain_id(evm_opts.networks, config.chain.map(|chain| chain.id()))?;
56 evm_opts.infer_network_from_fork().await?;
57 evm_opts.pin_fork_block().await?;
58 config.networks = evm_opts.networks;
59 let local_networks = evm_opts.networks;
60 let local_chain_id = evm_opts.env.chain_id.or(config.chain.map(|chain| chain.id()));
61
62 if evm_opts.networks.is_tempo() {
63 return Box::pin(run_command_with_network::<TempoEvmNetwork>(
64 args,
65 config,
66 evm_opts,
67 local_networks,
68 local_chain_id,
69 ))
70 .await;
71 }
72
73 #[cfg(feature = "monad")]
74 if evm_opts.networks.is_monad() {
75 return Box::pin(run_command_with_network::<foundry_evm::core::evm::MonadEvmNetwork>(
76 args,
77 config,
78 evm_opts,
79 local_networks,
80 local_chain_id,
81 ))
82 .await;
83 }
84
85 #[cfg(feature = "optimism")]
86 if evm_opts.networks.is_optimism() {
87 return Box::pin(run_command_with_network::<OpEvmNetwork>(
88 args,
89 config,
90 evm_opts,
91 local_networks,
92 local_chain_id,
93 ))
94 .await;
95 }
96
97 Box::pin(run_command_with_network::<EthEvmNetwork>(
98 args,
99 config,
100 evm_opts,
101 local_networks,
102 local_chain_id,
103 ))
104 .await
105}
106
107fn infer_network_from_chain_id(
108 networks: NetworkConfigs,
109 chain_id: Option<u64>,
110) -> Result<NetworkConfigs> {
111 if let Some(chain_id) = chain_id {
112 networks.try_with_chain_id(chain_id).map_err(eyre::Report::msg)
113 } else {
114 Ok(networks)
115 }
116}
117
118async fn run_command_with_network<FEN: FoundryEvmNetwork>(
119 args: Chisel,
120 config: Config,
121 evm_opts: EvmOpts,
122 local_networks: NetworkConfigs,
123 local_chain_id: Option<u64>,
124) -> Result<()> {
125 let fork_network_is_inferred = evm_opts.fork_network_is_inferred;
126 let fork_chain_id_is_inferred = evm_opts.fork_chain_id_is_inferred;
127 let mut dispatcher = ChiselDispatcher::<FEN>::new(crate::source::SessionSourceConfig {
129 traces: config.verbosity > 0,
131 foundry_config: config,
132 no_vm: args.no_vm,
133 evm_opts,
134 local_networks: Some(local_networks),
135 local_chain_id,
136 fork_network_is_inferred,
137 fork_chain_id_is_inferred,
138 resolved_hardfork: None,
139 source_chain_id: None,
140 cached_backend: None,
141 calldata: None,
142 ir_minimum: args.ir_minimum,
143 })?;
144
145 evaluate_prelude(&mut dispatcher, args.prelude).await?;
147
148 if let Some(cmd) = args.cmd {
149 try_cf!(handle_cli_command(&mut dispatcher, cmd).await?);
150 return Ok(());
151 }
152
153 let mut rl = Editor::<SolidityHelper, _>::new()?;
154 rl.set_helper(Some(dispatcher.helper.clone()));
155 rl.set_auto_add_history(true);
156 if let Some(path) = chisel_history_file() {
157 let _ = rl.load_history(&path);
158 }
159
160 sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?;
161
162 let mut interrupt = false;
164 loop {
165 let prompt = dispatcher.get_prompt();
166 match rl.readline(prompt.as_ref()) {
167 Ok(line) => {
168 debug!("dispatching next line: {line}");
169 interrupt = false;
171
172 let r = dispatcher.dispatch(&line).await;
174 dispatcher.helper.set_errored(r.is_err());
175 match r {
176 Ok(ControlFlow::Continue(())) => {}
177 Ok(ControlFlow::Break(())) => break,
178 Err(e) => {
179 sh_err!("{}", foundry_common::errors::display_chain(&e))?;
180 }
181 }
182 }
183 Err(ReadlineError::Interrupted) => {
184 if interrupt {
185 break;
186 }
187 sh_println!("(To exit, press Ctrl+C again)")?;
188 interrupt = true;
189 }
190 Err(ReadlineError::Eof) => break,
191 Err(err) => {
192 sh_err!("{err}")?;
193 break;
194 }
195 }
196 }
197
198 if let Some(path) = chisel_history_file() {
199 let _ = rl.save_history(&path);
200 }
201
202 Ok(())
203}
204
205async fn evaluate_prelude<FEN: FoundryEvmNetwork>(
208 dispatcher: &mut ChiselDispatcher<FEN>,
209 maybe_prelude: Option<PathBuf>,
210) -> Result<()> {
211 let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
212 if prelude_dir.is_file() {
213 sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
214 try_cf!(load_prelude_file(dispatcher, prelude_dir).await?);
215 sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
216 } else {
217 let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
218 let mut print_success_msg = false;
219 for source_file in prelude_sources {
220 print_success_msg = true;
221 sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
222 try_cf!(load_prelude_file(dispatcher, source_file).await?);
223 }
224
225 if print_success_msg {
226 sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
227 }
228 }
229 Ok(())
230}
231
232async fn load_prelude_file<FEN: FoundryEvmNetwork>(
234 dispatcher: &mut ChiselDispatcher<FEN>,
235 file: PathBuf,
236) -> Result<ControlFlow<()>> {
237 let prelude = fs::read_to_string(file)
238 .wrap_err("Could not load source file. Are you sure this path is correct?")?;
239 dispatcher.dispatch(&prelude).await
240}
241
242async fn handle_cli_command<FEN: FoundryEvmNetwork>(
243 d: &mut ChiselDispatcher<FEN>,
244 cmd: ChiselSubcommand,
245) -> Result<ControlFlow<()>> {
246 match cmd {
247 ChiselSubcommand::List => d.dispatch_command(ChiselCommand::ListSessions).await,
248 ChiselSubcommand::Load { id } => d.dispatch_command(ChiselCommand::Load { id }).await,
249 ChiselSubcommand::View { id } => {
250 let ControlFlow::Continue(()) = d.dispatch_command(ChiselCommand::Load { id }).await?
251 else {
252 return Ok(ControlFlow::Break(()));
253 };
254 d.dispatch_command(ChiselCommand::Source).await
255 }
256 ChiselSubcommand::ClearCache => d.dispatch_command(ChiselCommand::ClearCache).await,
257 ChiselSubcommand::Eval { command } => d.dispatch(&command).await,
258 }
259}
260
261fn chisel_history_file() -> Option<PathBuf> {
262 foundry_config::Config::foundry_dir().map(|p| p.join(".chisel_history"))
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use clap::CommandFactory;
269
270 #[test]
271 fn verify_cli() {
272 Chisel::command().debug_assert();
273 }
274
275 #[test]
276 #[cfg(not(feature = "monad"))]
277 fn chain_id_rejects_disabled_monad_network() {
278 let error = infer_network_from_chain_id(NetworkConfigs::default(), Some(143)).unwrap_err();
279
280 assert_eq!(
281 error.to_string(),
282 "cannot infer execution network from chain ID 143: network family `monad` is not \
283 enabled in this build"
284 );
285 }
286
287 #[test]
288 fn explicit_ethereum_overrides_chain_id_inference() {
289 let ethereum = NetworkConfigs::with_ethereum();
290 let inferred = infer_network_from_chain_id(ethereum, Some(143)).unwrap();
291
292 assert_eq!(inferred, ethereum);
293 }
294}