1use crate::{
2 history::chisel_history_file,
3 prelude::{ChiselCommand, ChiselDispatcher, DispatchResult, SolidityHelper},
4};
5use clap::Parser;
6use eyre::{Context, Result};
7use foundry_cli::{
8 handler,
9 utils::{self, LoadConfig},
10};
11use foundry_common::fs;
12use foundry_config::{
13 Config,
14 figment::{
15 Metadata, Profile, Provider,
16 value::{Dict, Map},
17 },
18};
19use rustyline::{Editor, config::Configurer, error::ReadlineError};
20use std::path::PathBuf;
21use tracing::debug;
22use yansi::Paint;
23
24use crate::opts::{Chisel, ChiselSubcommand};
25
26pub fn run() -> Result<()> {
28 setup()?;
29
30 let args = Chisel::parse();
31 args.global.init()?;
32 args.global.tokio_runtime().block_on(run_command(args))
33}
34
35pub fn setup() -> Result<()> {
37 utils::install_crypto_provider();
38 handler::install();
39 utils::subscriber();
40 utils::load_dotenv();
41
42 Ok(())
43}
44
45pub async fn run_command(args: Chisel) -> Result<()> {
47 let mut interrupt = false;
49
50 let (config, evm_opts) = args.load_config_and_evm_opts()?;
52
53 let mut dispatcher = ChiselDispatcher::new(crate::session_source::SessionSourceConfig {
55 traces: config.verbosity > 0,
57 foundry_config: config,
58 no_vm: args.no_vm,
59 evm_opts,
60 backend: None,
61 calldata: None,
62 })?;
63
64 evaluate_prelude(&mut dispatcher, args.prelude).await?;
66
67 match &args.cmd {
69 Some(ChiselSubcommand::List) => {
70 let sessions = dispatcher.dispatch_command(ChiselCommand::ListSessions, &[]).await;
71 match sessions {
72 DispatchResult::CommandSuccess(Some(session_list)) => {
73 sh_println!("{session_list}")?;
74 }
75 DispatchResult::CommandFailed(e) => sh_err!("{e}")?,
76 _ => panic!("Unexpected result: Please report this bug."),
77 }
78 return Ok(());
79 }
80 Some(ChiselSubcommand::Load { id }) | Some(ChiselSubcommand::View { id }) => {
81 match dispatcher.dispatch_command(ChiselCommand::Load, &[id]).await {
83 DispatchResult::CommandSuccess(_) => { }
84 DispatchResult::CommandFailed(e) => {
85 sh_err!("{e}")?;
86 return Ok(());
87 }
88 _ => panic!("Unexpected result! Please report this bug."),
89 }
90
91 if matches!(args.cmd, Some(ChiselSubcommand::View { .. })) {
93 match dispatcher.dispatch_command(ChiselCommand::Source, &[]).await {
94 DispatchResult::CommandSuccess(Some(source)) => {
95 sh_println!("{source}")?;
96 }
97 _ => panic!("Unexpected result! Please report this bug."),
98 }
99 return Ok(());
100 }
101 }
102 Some(ChiselSubcommand::ClearCache) => {
103 match dispatcher.dispatch_command(ChiselCommand::ClearCache, &[]).await {
104 DispatchResult::CommandSuccess(Some(msg)) => sh_println!("{}", msg.green())?,
105 DispatchResult::CommandFailed(e) => sh_err!("{e}")?,
106 _ => panic!("Unexpected result! Please report this bug."),
107 }
108 return Ok(());
109 }
110 Some(ChiselSubcommand::Eval { command }) => {
111 dispatch_repl_line(&mut dispatcher, command).await?;
112 return Ok(());
113 }
114 None => { }
115 }
116
117 let mut rl = Editor::<SolidityHelper, _>::new()?;
119 rl.set_helper(Some(SolidityHelper::default()));
120
121 rl.set_auto_add_history(true);
123
124 if let Some(chisel_history) = chisel_history_file() {
126 let _ = rl.load_history(&chisel_history);
127 }
128
129 sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?;
131
132 loop {
134 let prompt = dispatcher.get_prompt();
137
138 let next_string = rl.readline(prompt.as_ref());
140
141 match next_string {
143 Ok(line) => {
144 debug!("dispatching next line: {line}");
145 interrupt = false;
147
148 let errored = dispatch_repl_line(&mut dispatcher, &line).await?;
150 rl.helper_mut().unwrap().set_errored(errored);
151 }
152 Err(ReadlineError::Interrupted) => {
153 if interrupt {
154 break;
155 } else {
156 sh_println!("(To exit, press Ctrl+C again)")?;
157 interrupt = true;
158 }
159 }
160 Err(ReadlineError::Eof) => break,
161 Err(err) => {
162 sh_err!("{err:?}")?;
163 break;
164 }
165 }
166 }
167
168 if let Some(chisel_history) = chisel_history_file() {
169 let _ = rl.save_history(&chisel_history);
170 }
171
172 Ok(())
173}
174
175impl Provider for Chisel {
177 fn metadata(&self) -> Metadata {
178 Metadata::named("Script Args Provider")
179 }
180
181 fn data(&self) -> Result<Map<Profile, Dict>, foundry_config::figment::Error> {
182 Ok(Map::from([(Config::selected_profile(), Dict::default())]))
183 }
184}
185
186async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) -> Result<bool> {
188 let r = dispatcher.dispatch(line).await;
189 match &r {
190 DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => {
191 debug!(%line, ?msg, "dispatch success");
192 if let Some(msg) = msg {
193 sh_println!("{}", msg.green())?;
194 }
195 }
196 DispatchResult::UnrecognizedCommand(e) => sh_err!("{e}")?,
197 DispatchResult::SolangParserFailed(e) => {
198 sh_err!("{}", "Compilation error".red())?;
199 sh_eprintln!("{}", format!("{e:?}").red())?;
200 }
201 DispatchResult::FileIoError(e) => sh_err!("{}", format!("File IO - {e}").red())?,
202 DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => {
203 sh_err!("{}", msg.red())?
204 }
205 DispatchResult::Failure(None) => sh_err!(
206 "Please report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose"
207 )?,
208 }
209 Ok(r.is_error())
210}
211
212async fn evaluate_prelude(
215 dispatcher: &mut ChiselDispatcher,
216 maybe_prelude: Option<PathBuf>,
217) -> Result<()> {
218 let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
219 if prelude_dir.is_file() {
220 sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
221 load_prelude_file(dispatcher, prelude_dir).await?;
222 sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
223 } else {
224 let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
225 let mut print_success_msg = false;
226 for source_file in prelude_sources {
227 print_success_msg = true;
228 sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
229 load_prelude_file(dispatcher, source_file).await?;
230 }
231
232 if print_success_msg {
233 sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
234 }
235 }
236 Ok(())
237}
238
239async fn load_prelude_file(dispatcher: &mut ChiselDispatcher, file: PathBuf) -> Result<()> {
241 let prelude = fs::read_to_string(file)
242 .wrap_err("Could not load source file. Are you sure this path is correct?")?;
243 dispatch_repl_line(dispatcher, &prelude).await?;
244 Ok(())
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use clap::CommandFactory;
251
252 #[test]
253 fn verify_cli() {
254 Chisel::command().debug_assert();
255 }
256}