diff --git a/Cargo.lock b/Cargo.lock index d3b288ea1fe1..7d49bdea7831 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1332,6 +1332,7 @@ dependencies = [ "dirs 5.0.1", "eyre", "forge-fmt", + "foundry-block-explorers", "foundry-cli", "foundry-common", "foundry-compilers", diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index ae8f6c5b3e27..b5b3a2167c0e 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -20,6 +20,7 @@ vergen = { version = "8", default-features = false, features = ["build", "git", [dependencies] # forge forge-fmt.workspace = true +foundry-block-explorers.workspace = true foundry-cli.workspace = true foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["project-util", "full"] } diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index 69ae708405c5..a6819ebb6e60 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -182,8 +182,6 @@ async fn main() -> eyre::Result<()> { // Variable based on status of the last entry let prompt = dispatcher.get_prompt(); - rl.helper_mut().unwrap().set_errored(dispatcher.errored); - // Read the next line let next_string = rl.readline(prompt.as_ref()); @@ -195,7 +193,8 @@ async fn main() -> eyre::Result<()> { interrupt = false; // Dispatch and match results - dispatch_repl_line(&mut dispatcher, &line).await; + let errored = dispatch_repl_line(&mut dispatcher, &line).await; + rl.helper_mut().unwrap().set_errored(errored); } Err(ReadlineError::Interrupted) => { if interrupt { @@ -232,8 +231,9 @@ impl Provider for ChiselParser { } /// Evaluate a single Solidity line. -async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) { - match dispatcher.dispatch(line).await { +async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) -> bool { + let r = dispatcher.dispatch(line).await; + match &r { DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => { debug!(%line, ?msg, "dispatch success"); if let Some(msg) = msg { @@ -249,6 +249,7 @@ async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) { DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => eprintln!("{}", Paint::red(msg)), DispatchResult::Failure(None) => eprintln!("{}\nPlease Report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose", Paint::red("⚒️ Unknown Chisel Error ⚒️")), } + r.is_error() } /// Evaluate multiple Solidity source files contained within a diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 8cd552baee7d..67329f89c11b 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -3,9 +3,12 @@ //! This module contains the `ChiselDispatcher` struct, which handles the dispatching //! of both builtin commands and Solidity snippets. -use crate::prelude::{ - ChiselCommand, ChiselResult, ChiselSession, CmdCategory, CmdDescriptor, SessionSourceConfig, - SolidityHelper, +use crate::{ + prelude::{ + ChiselCommand, ChiselResult, ChiselSession, CmdCategory, CmdDescriptor, + SessionSourceConfig, SolidityHelper, + }, + session_source::SessionSource, }; use alloy_json_abi::JsonAbi; use alloy_primitives::{hex, Address}; @@ -23,7 +26,13 @@ use regex::Regex; use reqwest::Url; use serde::{Deserialize, Serialize}; use solang_parser::diagnostics::Diagnostic; -use std::{borrow::Cow, error::Error, io::Write, path::PathBuf, process::Command}; +use std::{ + borrow::Cow, + error::Error, + io::Write, + path::{Path, PathBuf}, + process::Command, +}; use strum::IntoEnumIterator; use tracing::debug; use yansi::Paint; @@ -47,8 +56,6 @@ static ADDRESS_RE: Lazy = Lazy::new(|| Regex::new(r"0x[a-fA-F0-9]{40}").u /// Chisel input dispatcher #[derive(Debug)] pub struct ChiselDispatcher { - /// The status of the previous dispatch - pub errored: bool, /// A Chisel Session pub session: ChiselSession, } @@ -72,6 +79,20 @@ pub enum DispatchResult { FileIoError(Box), } +impl DispatchResult { + /// Returns `true` if the result is an error. + pub fn is_error(&self) -> bool { + matches!( + self, + DispatchResult::Failure(_) | + DispatchResult::CommandFailed(_) | + DispatchResult::UnrecognizedCommand(_) | + DispatchResult::SolangParserFailed(_) | + DispatchResult::FileIoError(_) + ) + } +} + /// A response from the Etherscan API's `getabi` action #[derive(Debug, Serialize, Deserialize)] pub struct EtherscanABIResponse { @@ -113,7 +134,29 @@ pub fn format_source(source: &str, config: FormatterConfig) -> eyre::Result eyre::Result { - ChiselSession::new(config).map(|session| Self { errored: false, session }) + ChiselSession::new(config).map(|session| Self { session }) + } + + /// Returns the optional ID of the current session. + pub fn id(&self) -> Option<&str> { + self.session.id.as_deref() + } + + /// Returns the [`SessionSource`]. + pub fn source(&self) -> &SessionSource { + &self.session.session_source + } + + /// Returns the [`SessionSource`]. + pub fn source_mut(&mut self) -> &mut SessionSource { + &mut self.session.session_source + } + + fn format_source(&self) -> eyre::Result { + format_source( + &self.source().to_repl_source(), + self.source().config.foundry_config.fmt.clone(), + ) } /// Returns the prompt based on the current status of the Dispatcher @@ -188,18 +231,10 @@ impl ChiselDispatcher { std::process::exit(0); } ChiselCommand::Clear => { - if let Some(session_source) = self.session.session_source.as_mut() { - // Drain all source sections - session_source.drain_run(); - session_source.drain_global_code(); - session_source.drain_top_level_code(); - - DispatchResult::CommandSuccess(Some(String::from("Cleared session!"))) - } else { - DispatchResult::CommandFailed( - Paint::red("Session source not present!").to_string(), - ) - } + self.source_mut().drain_run(); + self.source_mut().drain_global_code(); + self.source_mut().drain_top_level_code(); + DispatchResult::CommandSuccess(Some(String::from("Cleared session!"))) } ChiselCommand::Save => { if args.len() <= 1 { @@ -232,15 +267,14 @@ impl ChiselDispatcher { // Use args as the name let name = args[0]; // Try to save the current session before loading another - if let Some(session_source) = &self.session.session_source { - // Don't save an empty session - if !session_source.run_code.is_empty() { - if let Err(e) = self.session.write() { - return DispatchResult::FileIoError(e.into()) - } - println!("{}", Paint::green("Saved current session!")); + // Don't save an empty session + if !self.source().run_code.is_empty() { + if let Err(e) = self.session.write() { + return DispatchResult::FileIoError(e.into()) } + println!("{}", Paint::green("Saved current session!")); } + // Parse the arguments let new_session = match name { "latest" => ChiselSession::latest(), @@ -254,7 +288,7 @@ impl ChiselDispatcher { // SAFETY // Should never panic due to the checks performed when the session was created // in the first place. - new_session.session_source.as_mut().unwrap().build().unwrap(); + new_session.session_source.build().unwrap(); self.session = new_session; DispatchResult::CommandSuccess(Some(format!( @@ -281,23 +315,14 @@ impl ChiselDispatcher { "No sessions found. Use the `!save` command to save a session.", )), }, - ChiselCommand::Source => { - if let Some(session_source) = self.session.session_source.as_ref() { - match format_source( - &session_source.to_repl_source(), - session_source.config.foundry_config.fmt.clone(), - ) { - Ok(formatted_source) => DispatchResult::CommandSuccess(Some( - SolidityHelper::highlight(&formatted_source).into_owned(), - )), - Err(_) => DispatchResult::CommandFailed(String::from( - "Failed to format session source", - )), - } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + ChiselCommand::Source => match self.format_source() { + Ok(formatted_source) => DispatchResult::CommandSuccess(Some( + SolidityHelper::highlight(&formatted_source).into_owned(), + )), + Err(_) => { + DispatchResult::CommandFailed(String::from("Failed to format session source")) } - } + }, ChiselCommand::ClearCache => match ChiselSession::clear_cache() { Ok(_) => { self.session.id = None; @@ -306,191 +331,158 @@ impl ChiselDispatcher { Err(_) => DispatchResult::CommandFailed(Self::make_error("Failed to clear cache!")), }, ChiselCommand::Fork => { - if let Some(session_source) = self.session.session_source.as_mut() { - if args.is_empty() || args[0].trim().is_empty() { - session_source.config.evm_opts.fork_url = None; - return DispatchResult::CommandSuccess(Some( - "Now using local environment.".to_string(), - )) - } - if args.len() != 1 { - return DispatchResult::CommandFailed(Self::make_error( - "Must supply a session ID as the argument.", - )) + if args.is_empty() || args[0].trim().is_empty() { + self.source_mut().config.evm_opts.fork_url = None; + return DispatchResult::CommandSuccess(Some( + "Now using local environment.".to_string(), + )) + } + if args.len() != 1 { + return DispatchResult::CommandFailed(Self::make_error( + "Must supply a session ID as the argument.", + )) + } + let arg = *args.first().unwrap(); + + // If the argument is an RPC alias designated in the + // `[rpc_endpoints]` section of the `foundry.toml` within + // the pwd, use the URL matched to the key. + let endpoint = if let Some(endpoint) = + self.source_mut().config.foundry_config.rpc_endpoints.get(arg) + { + endpoint.clone() + } else { + RpcEndpoint::Env(arg.to_string()).into() + }; + let fork_url = match endpoint.resolve() { + Ok(fork_url) => fork_url, + Err(e) => { + return DispatchResult::CommandFailed(Self::make_error(format!( + "\"{}\" ENV Variable not set!", + e.var + ))) } - let arg = *args.first().unwrap(); - - // If the argument is an RPC alias designated in the - // `[rpc_endpoints]` section of the `foundry.toml` within - // the pwd, use the URL matched to the key. - let endpoint = if let Some(endpoint) = - session_source.config.foundry_config.rpc_endpoints.get(arg) - { - endpoint.clone() - } else { - RpcEndpoint::Env(arg.to_string()).into() - }; - let fork_url = match endpoint.resolve() { - Ok(fork_url) => fork_url, - Err(e) => { - return DispatchResult::CommandFailed(Self::make_error(format!( - "\"{}\" ENV Variable not set!", - e.var - ))) - } - }; + }; - // Check validity of URL - if Url::parse(&fork_url).is_err() { - return DispatchResult::CommandFailed(Self::make_error("Invalid fork URL!")) - } + // Check validity of URL + if Url::parse(&fork_url).is_err() { + return DispatchResult::CommandFailed(Self::make_error("Invalid fork URL!")) + } - // Create success message before moving the fork_url - let success_msg = format!("Set fork URL to {}", Paint::yellow(&fork_url)); + // Create success message before moving the fork_url + let success_msg = format!("Set fork URL to {}", Paint::yellow(&fork_url)); - // Update the fork_url inside of the [SessionSourceConfig]'s [EvmOpts] - // field - session_source.config.evm_opts.fork_url = Some(fork_url); + // Update the fork_url inside of the [SessionSourceConfig]'s [EvmOpts] + // field + self.source_mut().config.evm_opts.fork_url = Some(fork_url); - // Clear the backend so that it is re-instantiated with the new fork - // upon the next execution of the session source. - session_source.config.backend = None; + // Clear the backend so that it is re-instantiated with the new fork + // upon the next execution of the session source. + self.source_mut().config.backend = None; - DispatchResult::CommandSuccess(Some(success_msg)) - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) - } + DispatchResult::CommandSuccess(Some(success_msg)) } ChiselCommand::Traces => { - if let Some(session_source) = self.session.session_source.as_mut() { - session_source.config.traces = !session_source.config.traces; - DispatchResult::CommandSuccess(Some(format!( - "{} traces!", - if session_source.config.traces { "Enabled" } else { "Disabled" } - ))) - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) - } + self.source_mut().config.traces = !self.source_mut().config.traces; + DispatchResult::CommandSuccess(Some(format!( + "{} traces!", + if self.source_mut().config.traces { "Enabled" } else { "Disabled" } + ))) } ChiselCommand::Calldata => { - if let Some(session_source) = self.session.session_source.as_mut() { - // remove empty space, double quotes, and 0x prefix - let arg = args - .first() - .map(|s| { - s.trim_matches(|c: char| c.is_whitespace() || c == '"' || c == '\'') - }) - .map(|s| s.strip_prefix("0x").unwrap_or(s)) - .unwrap_or(""); - - if arg.is_empty() { - session_source.config.calldata = None; - return DispatchResult::CommandSuccess(Some("Calldata cleared.".to_string())) - } + // remove empty space, double quotes, and 0x prefix + let arg = args + .first() + .map(|s| s.trim_matches(|c: char| c.is_whitespace() || c == '"' || c == '\'')) + .map(|s| s.strip_prefix("0x").unwrap_or(s)) + .unwrap_or(""); + + if arg.is_empty() { + self.source_mut().config.calldata = None; + return DispatchResult::CommandSuccess(Some("Calldata cleared.".to_string())) + } - let calldata = hex::decode(arg); - match calldata { - Ok(calldata) => { - session_source.config.calldata = Some(calldata); - DispatchResult::CommandSuccess(Some(format!( - "Set calldata to '{}'", - Paint::yellow(arg) - ))) - } - Err(e) => DispatchResult::CommandFailed(Self::make_error(format!( - "Invalid calldata: {}", - e - ))), + let calldata = hex::decode(arg); + match calldata { + Ok(calldata) => { + self.source_mut().config.calldata = Some(calldata); + DispatchResult::CommandSuccess(Some(format!( + "Set calldata to '{}'", + Paint::yellow(arg) + ))) } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(e) => DispatchResult::CommandFailed(Self::make_error(format!( + "Invalid calldata: {}", + e + ))), } } ChiselCommand::MemDump | ChiselCommand::StackDump => { - if let Some(session_source) = self.session.session_source.as_mut() { - match session_source.execute().await { - Ok((_, res)) => { - if let Some((stack, mem, _)) = res.state.as_ref() { - if matches!(cmd, ChiselCommand::MemDump) { - // Print memory by word - (0..mem.len()).step_by(32).for_each(|i| { - println!( - "{}: {}", - Paint::yellow(format!( - "[0x{:02x}:0x{:02x}]", - i, - i + 32 - )), - Paint::cyan(hex::encode_prefixed(&mem[i..i + 32])) - ); - }); - } else { - // Print all stack items - (0..stack.len()).rev().for_each(|i| { - println!( - "{}: {}", - Paint::yellow(format!("[{}]", stack.len() - i - 1)), - Paint::cyan(format!("0x{:02x}", stack.data()[i])) - ); - }); - } - DispatchResult::CommandSuccess(None) + match self.source_mut().execute().await { + Ok((_, res)) => { + if let Some((stack, mem, _)) = res.state.as_ref() { + if matches!(cmd, ChiselCommand::MemDump) { + // Print memory by word + (0..mem.len()).step_by(32).for_each(|i| { + println!( + "{}: {}", + Paint::yellow(format!("[0x{:02x}:0x{:02x}]", i, i + 32)), + Paint::cyan(hex::encode_prefixed(&mem[i..i + 32])) + ); + }); } else { - DispatchResult::CommandFailed(Self::make_error( - "Run function is empty.", - )) + // Print all stack items + (0..stack.len()).rev().for_each(|i| { + println!( + "{}: {}", + Paint::yellow(format!("[{}]", stack.len() - i - 1)), + Paint::cyan(format!("0x{:02x}", stack.data()[i])) + ); + }); } + DispatchResult::CommandSuccess(None) + } else { + DispatchResult::CommandFailed(Self::make_error( + "Run function is empty.", + )) } - Err(e) => DispatchResult::CommandFailed(Self::make_error(e.to_string())), } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(e) => DispatchResult::CommandFailed(Self::make_error(e.to_string())), } } ChiselCommand::Export => { // Check if the current session inherits `Script.sol` before exporting - if let Some(session_source) = self.session.session_source.as_ref() { - // Check if the pwd is a foundry project - if PathBuf::from("foundry.toml").exists() { - // Create "script" dir if it does not already exist. - if !PathBuf::from("script").exists() { - if let Err(e) = std::fs::create_dir_all("script") { - return DispatchResult::CommandFailed(Self::make_error( - e.to_string(), - )) - } - } - match format_source( - &session_source.to_script_source(), - session_source.config.foundry_config.fmt.clone(), - ) { - Ok(formatted_source) => { - // Write session source to `script/REPL.s.sol` - if let Err(e) = std::fs::write( - PathBuf::from("script/REPL.s.sol"), - formatted_source, - ) { - return DispatchResult::CommandFailed(Self::make_error( - e.to_string(), - )) - } + // Check if the pwd is a foundry project + if !Path::new("foundry.toml").exists() { + return DispatchResult::CommandFailed(Self::make_error( + "Must be in a foundry project to export source to script.", + )); + } - DispatchResult::CommandSuccess(Some(String::from( - "Exported session source to script/REPL.s.sol!", - ))) - } - Err(_) => DispatchResult::CommandFailed(String::from( - "Failed to format session source", - )), + // Create "script" dir if it does not already exist. + if !Path::new("script").exists() { + if let Err(e) = std::fs::create_dir_all("script") { + return DispatchResult::CommandFailed(Self::make_error(e.to_string())) + } + } + + match self.format_source() { + Ok(formatted_source) => { + // Write session source to `script/REPL.s.sol` + if let Err(e) = + std::fs::write(PathBuf::from("script/REPL.s.sol"), formatted_source) + { + return DispatchResult::CommandFailed(Self::make_error(e.to_string())) } - } else { - DispatchResult::CommandFailed(Self::make_error( - "Must be in a foundry project to export source to script.", - )) + + DispatchResult::CommandSuccess(Some(String::from( + "Exported session source to script/REPL.s.sol!", + ))) } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(_) => DispatchResult::CommandFailed(String::from( + "Failed to format session source", + )), } } ChiselCommand::Fetch => { @@ -503,15 +495,8 @@ impl ChiselDispatcher { let request_url = format!( "https://api.etherscan.io/api?module=contract&action=getabi&address={}{}", args[0], - if let Some(api_key) = self - .session - .session_source - .as_ref() - .unwrap() - .config - .foundry_config - .etherscan_api_key - .as_ref() + if let Some(api_key) = + self.source().config.foundry_config.etherscan_api_key.as_ref() { format!("&apikey={api_key}") } else { @@ -602,11 +587,7 @@ impl ChiselDispatcher { // Add the interface to the source outright - no need to verify // syntax via compilation and/or // parsing. - self.session - .session_source - .as_mut() - .unwrap() - .with_global_code(&interface); + self.source_mut().with_global_code(&interface); DispatchResult::CommandSuccess(Some(format!( "Added {}'s interface to source as `{}`", @@ -653,97 +634,91 @@ impl ChiselDispatcher { } } ChiselCommand::Edit => { - if let Some(session_source) = self.session.session_source.as_mut() { - // create a temp file with the content of the run code - let mut temp_file_path = std::env::temp_dir(); - temp_file_path.push("chisel-tmp.sol"); - let result = std::fs::File::create(&temp_file_path) - .map(|mut file| file.write_all(session_source.run_code.as_bytes())); - if let Err(e) = result { - return DispatchResult::CommandFailed(format!( - "Could not write to a temporary file: {e}" - )) - } + // create a temp file with the content of the run code + let mut temp_file_path = std::env::temp_dir(); + temp_file_path.push("chisel-tmp.sol"); + let result = std::fs::File::create(&temp_file_path) + .map(|mut file| file.write_all(self.source().run_code.as_bytes())); + if let Err(e) = result { + return DispatchResult::CommandFailed(format!( + "Could not write to a temporary file: {e}" + )) + } - // open the temp file with the editor - let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string()); - let mut cmd = Command::new(editor); - cmd.arg(&temp_file_path); - - match cmd.status() { - Ok(status) => { - if !status.success() { - if let Some(status_code) = status.code() { - return DispatchResult::CommandFailed(format!( - "Editor exited with status {status_code}" - )) - } else { - return DispatchResult::CommandFailed( - "Editor exited without a status code".to_string(), - ) - } + // open the temp file with the editor + let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string()); + let mut cmd = Command::new(editor); + cmd.arg(&temp_file_path); + + match cmd.status() { + Ok(status) => { + if !status.success() { + if let Some(status_code) = status.code() { + return DispatchResult::CommandFailed(format!( + "Editor exited with status {status_code}" + )) + } else { + return DispatchResult::CommandFailed( + "Editor exited without a status code".to_string(), + ) } } - Err(_) => { - return DispatchResult::CommandFailed( - "Editor exited without a status code".to_string(), - ) - } } - - let mut new_session_source = session_source.clone(); - if let Ok(edited_code) = std::fs::read_to_string(temp_file_path) { - new_session_source.drain_run(); - new_session_source.with_run_code(&edited_code); - } else { + Err(_) => { return DispatchResult::CommandFailed( - "Could not read the edited file".to_string(), + "Editor exited without a status code".to_string(), ) } + } - // if the editor exited successfully, try to compile the new code - match new_session_source.execute().await { - Ok((_, mut res)) => { - let failed = !res.success; - if new_session_source.config.traces || failed { - if let Ok(decoder) = - Self::decode_traces(&new_session_source.config, &mut res) - { - if let Err(e) = Self::show_traces(&decoder, &mut res).await { - self.errored = true; - return DispatchResult::CommandFailed(e.to_string()) - }; - - // Show console logs, if there are any - let decoded_logs = decode_console_logs(&res.logs); - if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); - for log in decoded_logs { - println!(" {log}"); - } + let mut new_session_source = self.source().clone(); + if let Ok(edited_code) = std::fs::read_to_string(temp_file_path) { + new_session_source.drain_run(); + new_session_source.with_run_code(&edited_code); + } else { + return DispatchResult::CommandFailed( + "Could not read the edited file".to_string(), + ) + } + + // if the editor exited successfully, try to compile the new code + match new_session_source.execute().await { + Ok((_, mut res)) => { + let failed = !res.success; + if new_session_source.config.traces || failed { + if let Ok(decoder) = + Self::decode_traces(&new_session_source.config, &mut res) + { + if let Err(e) = Self::show_traces(&decoder, &mut res).await { + return DispatchResult::CommandFailed(e.to_string()) + }; + + // Show console logs, if there are any + let decoded_logs = decode_console_logs(&res.logs); + if !decoded_logs.is_empty() { + println!("{}", Paint::green("Logs:")); + for log in decoded_logs { + println!(" {log}"); } } - - // If the contract execution failed, continue on without - // updating the source. - self.errored = true; - DispatchResult::CommandFailed(Self::make_error( - "Failed to execute edited contract!", - )) - } else { - // the code could be compiled, save it - *session_source = new_session_source; - DispatchResult::CommandSuccess(Some(String::from( - "Successfully edited `run()` function's body!", - ))) } + + // If the contract execution failed, continue on without + // updating the source. + DispatchResult::CommandFailed(Self::make_error( + "Failed to execute edited contract!", + )) + } else { + // the code could be compiled, save it + *self.source_mut() = new_session_source; + DispatchResult::CommandSuccess(Some(String::from( + "Successfully edited `run()` function's body!", + ))) } - Err(_) => DispatchResult::CommandFailed( - "The code could not be compiled".to_string(), - ), } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(_) => { + DispatchResult::CommandFailed("The code could not be compiled".to_string()) + } } } ChiselCommand::RawStack => { @@ -760,14 +735,7 @@ impl ChiselDispatcher { let to_inspect = args.first().unwrap(); // Get a mutable reference to the session source - let source = match self.session.session_source.as_mut() { - Some(session_source) => session_source, - _ => { - return DispatchResult::CommandFailed( - "Session source not present".to_string(), - ) - } - }; + let source = self.source_mut(); // Copy the variable's stack contents into a bytes32 variable without updating // the current session source. @@ -796,15 +764,8 @@ impl ChiselDispatcher { let raw_cmd = &split[0][1..]; return match raw_cmd.parse::() { - Ok(cmd) => { - let command_dispatch = self.dispatch_command(cmd, &split[1..]).await; - self.errored = !matches!(command_dispatch, DispatchResult::CommandSuccess(_)); - command_dispatch - } - Err(e) => { - self.errored = true; - DispatchResult::UnrecognizedCommand(e) - } + Ok(cmd) => self.dispatch_command(cmd, &split[1..]).await, + Err(e) => DispatchResult::UnrecognizedCommand(e), } } if input.trim().is_empty() { @@ -813,14 +774,7 @@ impl ChiselDispatcher { } // Get a mutable reference to the session source - let source = match self.session.session_source.as_mut().ok_or(DispatchResult::Failure(None)) - { - Ok(project) => project, - Err(e) => { - self.errored = true; - return e - } - }; + let source = self.source_mut(); // If the input is a comment, add it to the run code so we avoid running with empty input if COMMENT_RE.is_match(input) { @@ -847,7 +801,6 @@ impl ChiselDispatcher { let (mut new_source, do_execute) = match source.clone_with_new_line(input.to_string()) { Ok(new) => new, Err(e) => { - self.errored = true; return DispatchResult::CommandFailed(Self::make_error(format!( "Failed to parse input! {e}" ))) @@ -867,10 +820,7 @@ impl ChiselDispatcher { } // Return with the error - Err(e) => { - self.errored = true; - return DispatchResult::CommandFailed(Self::make_error(e)) - } + Err(e) => return DispatchResult::CommandFailed(Self::make_error(e)), } if do_execute { @@ -883,7 +833,6 @@ impl ChiselDispatcher { if new_source.config.traces || failed { if let Ok(decoder) = Self::decode_traces(&new_source.config, &mut res) { if let Err(e) = Self::show_traces(&decoder, &mut res).await { - self.errored = true; return DispatchResult::CommandFailed(e.to_string()) }; @@ -899,7 +848,6 @@ impl ChiselDispatcher { // If the contract execution failed, continue on without adding the new // line to the source. if failed { - self.errored = true; return DispatchResult::Failure(Some(Self::make_error( "Failed to execute REPL contract!", ))) @@ -908,29 +856,20 @@ impl ChiselDispatcher { } // Replace the old session source with the new version - self.session.session_source = Some(new_source); - // Clear any outstanding errors - self.errored = false; + *self.source_mut() = new_source; DispatchResult::Success(None) } - Err(e) => { - self.errored = true; - DispatchResult::Failure(Some(e.to_string())) - } + Err(e) => DispatchResult::Failure(Some(e.to_string())), } } else { match new_source.build() { Ok(out) => { debug!(%input, ?out, "skipped execute and rebuild source"); - self.session.session_source = Some(new_source); - self.errored = false; + *self.source_mut() = new_source; DispatchResult::Success(None) } - Err(e) => { - self.errored = true; - DispatchResult::Failure(Some(e.to_string())) - } + Err(e) => DispatchResult::Failure(Some(e.to_string())), } } } diff --git a/crates/chisel/src/session.rs b/crates/chisel/src/session.rs index f0f1debd5a70..3be34a179cc9 100644 --- a/crates/chisel/src/session.rs +++ b/crates/chisel/src/session.rs @@ -13,7 +13,7 @@ use time::{format_description, OffsetDateTime}; #[derive(Debug, Serialize, Deserialize)] pub struct ChiselSession { /// The `SessionSource` object that houses the REPL session. - pub session_source: Option, + pub session_source: SessionSource, /// The current session's identifier pub id: Option, } @@ -31,9 +31,8 @@ impl ChiselSession { /// A new instance of [ChiselSession] pub fn new(config: SessionSourceConfig) -> Result { let solc = config.solc()?; - // Return initialized ChiselSession with set solc version - Ok(Self { session_source: Some(SessionSource::new(solc, config)), id: None }) + Ok(Self { session_source: SessionSource::new(solc, config), id: None }) } /// Render the full source code for the current session. @@ -47,11 +46,7 @@ impl ChiselSession { /// This function will not panic, but will return a blank string if the /// session's [SessionSource] is None. pub fn contract_source(&self) -> String { - if let Some(source) = &self.session_source { - source.to_repl_source() - } else { - String::default() - } + self.session_source.to_repl_source() } /// Clears the cache directory diff --git a/crates/chisel/tests/cache.rs b/crates/chisel/tests/cache.rs index dda53cd62382..ff5fe60fc91b 100644 --- a/crates/chisel/tests/cache.rs +++ b/crates/chisel/tests/cache.rs @@ -163,10 +163,7 @@ fn test_load_cache() { assert!(new_env.is_ok()); let new_env = new_env.unwrap(); assert_eq!(new_env.id.unwrap(), String::from("0")); - assert_eq!( - new_env.session_source.unwrap().to_repl_source(), - env.session_source.unwrap().to_repl_source() - ); + assert_eq!(new_env.session_source.to_repl_source(), env.session_source.to_repl_source()); } #[test] @@ -225,8 +222,5 @@ fn test_load_latest_cache() { // Validate the session assert_eq!(new_env.id.unwrap(), "1"); - assert_eq!( - new_env.session_source.unwrap().to_repl_source(), - env.session_source.unwrap().to_repl_source() - ); + assert_eq!(new_env.session_source.to_repl_source(), env.session_source.to_repl_source()); }