Skip to main content

chisel/
args.rs

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
21/// Run the `chisel` command line interface.
22pub 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
32/// Setup the global logger and other utilities.
33pub 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
49/// Run the subcommand.
50pub async fn run_command(args: Chisel) -> Result<()> {
51    // Load configuration
52    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    // Create a new cli dispatcher
128    let mut dispatcher = ChiselDispatcher::<FEN>::new(crate::source::SessionSourceConfig {
129        // Enable traces if any level of verbosity was passed
130        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    // Execute prelude Solidity source files
146    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    // REPL loop.
163    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                // Clear interrupt flag.
170                interrupt = false;
171
172                // Dispatch and match results.
173                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
205/// Evaluate multiple Solidity source files contained within a
206/// Chisel prelude directory.
207async 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
232/// Loads a single Solidity file into the prelude.
233async 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}