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