diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index f524e458d2fe..10a53e18d874 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -120,14 +120,17 @@ mod tests { } fn sol_iface() -> String { - let cheats = Cheatcodes::new().to_string().trim().replace('\n', "\n "); + let mut cheats = Cheatcodes::new(); + cheats.errors = Default::default(); // Skip errors to allow <0.8.4. + let cheats = cheats.to_string().trim().replace('\n', "\n "); format!( "\ // Automatically generated from `foundry-cheatcodes` Vm definitions. Do not modify manually. // This interface is just for internal testing purposes. Use `forge-std` instead. // SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.4; +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; interface Vm {{ {cheats} diff --git a/crates/chisel/benches/session_source.rs b/crates/chisel/benches/session_source.rs index 3088c3efe41f..3c3196956b95 100644 --- a/crates/chisel/benches/session_source.rs +++ b/crates/chisel/benches/session_source.rs @@ -1,8 +1,6 @@ use chisel::session_source::{SessionSource, SessionSourceConfig}; use criterion::{criterion_group, Criterion}; use foundry_compilers::Solc; -use foundry_config::Config; -use foundry_evm::opts::EvmOpts; use once_cell::sync::Lazy; use std::hint::black_box; use tokio::runtime::Runtime; @@ -66,16 +64,7 @@ fn inspect(c: &mut Criterion) { /// Helper function for getting an empty [SessionSource] with default configuration fn get_empty_session_source() -> SessionSource { - SessionSource::new( - SOLC.clone(), - SessionSourceConfig { - foundry_config: Config::default(), - evm_opts: EvmOpts::default(), - backend: None, - traces: false, - calldata: None, - }, - ) + SessionSource::new(SOLC.clone(), SessionSourceConfig::default()) } fn rt() -> Runtime { diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index a6819ebb6e60..d9028965a627 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -53,6 +53,14 @@ pub struct ChiselParser { #[clap(long, help_heading = "REPL options")] pub prelude: Option, + /// Disable the default `Vm` import. + #[clap(long, help_heading = "REPL options", long_help = format!( + "Disable the default `Vm` import.\n\n\ + The import is disabled by default if the Solc version is less than {}.", + chisel::session_source::MIN_VM_VERSION + ))] + pub no_vm: bool, + #[clap(flatten)] pub opts: CoreBuildArgs, @@ -107,6 +115,7 @@ async fn main() -> eyre::Result<()> { // Enable traces if any level of verbosity was passed traces: config.verbosity > 0, foundry_config: config, + no_vm: args.no_vm, evm_opts, backend: None, calldata: None, diff --git a/crates/chisel/src/session_source.rs b/crates/chisel/src/session_source.rs index 3e4efa2fd8c8..322598e36940 100644 --- a/crates/chisel/src/session_source.rs +++ b/crates/chisel/src/session_source.rs @@ -18,6 +18,9 @@ use solang_parser::pt; use std::{collections::HashMap, fs, path::PathBuf}; use yansi::Paint; +/// The minimum Solidity version of the `Vm` interface. +pub const MIN_VM_VERSION: Version = Version::new(0, 6, 2); + /// Solidity source for the `Vm` interface in [forge-std](https://github.com/foundry-rs/forge-std) static VM_SOURCE: &str = include_str!("../../../testdata/cheats/Vm.sol"); @@ -69,6 +72,8 @@ pub struct SessionSourceConfig { pub foundry_config: Config, /// EVM Options pub evm_opts: EvmOpts, + /// Disable the default `Vm` import. + pub no_vm: bool, #[serde(skip)] /// In-memory REVM db for the session's runner. pub backend: Option, @@ -184,9 +189,13 @@ impl SessionSource { /// /// A new instance of [SessionSource] #[track_caller] - pub fn new(solc: Solc, config: SessionSourceConfig) -> Self { - #[cfg(debug_assertions)] - let _ = solc.version().unwrap(); + pub fn new(solc: Solc, mut config: SessionSourceConfig) -> Self { + if let Ok(v) = solc.version_short() { + if v < MIN_VM_VERSION && !config.no_vm { + tracing::info!(version=%v, minimum=%MIN_VM_VERSION, "Disabling VM injection"); + config.no_vm = true; + } + } Self { file_name: PathBuf::from("ReplContract.sol".to_string()), @@ -315,14 +324,15 @@ impl SessionSource { sources.insert(self.file_name.clone(), Source::new(self.to_repl_source())); // Include Vm.sol if forge-std remapping is not available - if !self - .config - .foundry_config - .get_all_remappings() - .into_iter() - .any(|r| r.name.starts_with("forge-std")) + if !self.config.no_vm && + !self + .config + .foundry_config + .get_all_remappings() + .into_iter() + .any(|r| r.name.starts_with("forge-std")) { - sources.insert(PathBuf::from("forge-std/Vm.sol"), Source::new(VM_SOURCE.to_owned())); + sources.insert(PathBuf::from("forge-std/Vm.sol"), Source::new(VM_SOURCE)); } // we only care about the solidity source, so we can safely unwrap @@ -446,24 +456,27 @@ impl SessionSource { /// The [SessionSource] represented as a Forge Script contract. pub fn to_script_source(&self) -> String { let Version { major, minor, patch, .. } = self.solc.version().unwrap(); + let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; + + let script_import = + if !config.no_vm { "import {Script} from \"forge-std/Script.sol\";\n" } else { "" }; + format!( r#" // SPDX-License-Identifier: UNLICENSED pragma solidity ^{major}.{minor}.{patch}; -import {{Script}} from "forge-std/Script.sol"; -{} +{script_import} +{global_code} -contract {} is Script {{ - {} - +contract {contract_name} is Script {{ + {top_level_code} + /// @notice Script entry point function run() public {{ - {} + {run_code} }} -}} - "#, - self.global_code, self.contract_name, self.top_level_code, self.run_code, +}}"#, ) } @@ -474,25 +487,34 @@ contract {} is Script {{ /// The [SessionSource] represented as a REPL contract. pub fn to_repl_source(&self) -> String { let Version { major, minor, patch, .. } = self.solc.version().unwrap(); + let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; + + let (vm_import, vm_constant) = if !config.no_vm { + ( + "import {Vm} from \"forge-std/Vm.sol\";\n", + "Vm internal constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n" + ) + } else { + ("", "") + }; + format!( r#" // SPDX-License-Identifier: UNLICENSED pragma solidity ^{major}.{minor}.{patch}; -import {{Vm}} from "forge-std/Vm.sol"; -{} +{vm_import} +{global_code} -contract {} {{ - Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - {} +contract {contract_name} {{ + {vm_constant} + {top_level_code} /// @notice REPL contract entry point function run() public {{ - {} + {run_code} }} -}} - "#, - self.global_code, self.contract_name, self.top_level_code, self.run_code, +}}"#, ) } @@ -646,14 +668,17 @@ pub fn parse_fragment( ) -> Option { let mut base = SessionSource::new(solc, config); - if base.clone().with_run_code(buffer).parse().is_ok() { - return Some(ParseTreeFragment::Function) + match base.clone().with_run_code(buffer).parse() { + Ok(_) => return Some(ParseTreeFragment::Function), + Err(e) => tracing::debug!(?e), } - if base.clone().with_top_level_code(buffer).parse().is_ok() { - return Some(ParseTreeFragment::Contract) + match base.clone().with_top_level_code(buffer).parse() { + Ok(_) => return Some(ParseTreeFragment::Contract), + Err(e) => tracing::debug!(?e), } - if base.with_global_code(buffer).parse().is_ok() { - return Some(ParseTreeFragment::Source) + match base.with_global_code(buffer).parse() { + Ok(_) => return Some(ParseTreeFragment::Source), + Err(e) => tracing::debug!(?e), } None diff --git a/crates/chisel/tests/cache.rs b/crates/chisel/tests/cache.rs index ff5fe60fc91b..9173e8bc791d 100644 --- a/crates/chisel/tests/cache.rs +++ b/crates/chisel/tests/cache.rs @@ -1,7 +1,6 @@ use chisel::session::ChiselSession; use foundry_compilers::EvmVersion; use foundry_config::Config; -use foundry_evm::opts::EvmOpts; use serial_test::serial; use std::path::Path; @@ -43,10 +42,7 @@ fn test_write_session() { // Create a new session let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { foundry_config, - evm_opts: EvmOpts::default(), - backend: None, - traces: false, - calldata: None, + ..Default::default() }) .unwrap_or_else(|e| panic!("Failed to create ChiselSession!, {}", e)); diff --git a/crates/evm/core/src/fork/backend.rs b/crates/evm/core/src/fork/backend.rs index 0748c2eb98e8..eb3638ecea87 100644 --- a/crates/evm/core/src/fork/backend.rs +++ b/crates/evm/core/src/fork/backend.rs @@ -174,13 +174,14 @@ where entry.get_mut().push(listener); } Entry::Vacant(entry) => { - trace!(target: "backendhandler", "preparing storage request, address={:?}, idx={}", address, idx); + trace!(target: "backendhandler", %address, %idx, "preparing storage request"); entry.insert(vec![listener]); let provider = self.provider.clone(); let block_id = self.block_id; let fut = Box::pin(async move { - let storage = provider.get_storage_at(address, idx, block_id).await; - (storage.wrap_err("could not fetch slot {idx} from {address}"), address, idx) + let storage = + provider.get_storage_at(address, idx, block_id).await.map_err(Into::into); + (storage, address, idx) }); self.pending_requests.push(ProviderRequest::Storage(fut)); } diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 82a4b84358fc..7a1be2721b9a 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -2,10 +2,10 @@ // This interface is just for internal testing purposes. Use `forge-std` instead. // SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.4; +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; interface Vm { - error CheatcodeError(string message); enum CallerMode { None, Broadcast, RecurrentBroadcast, Prank, RecurrentPrank } enum AccountAccessKind { Call, DelegateCall, CallCode, StaticCall, Create, SelfDestruct, Resume, Balance, Extcodesize, Extcodehash, Extcodecopy } struct Log { bytes32[] topics; bytes data; address emitter; }