diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 7d7c922b4b6d..b5fe3e81879a 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -6,9 +6,9 @@ use clap::Parser; use eyre::{Result, WrapErr}; use foundry_cli::{ opts::RpcOpts, - utils::{handle_traces, init_progress, TraceResult}, + utils::{cache_local_signatures, handle_traces, init_progress, TraceResult}, }; -use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; +use foundry_common::{compile::ProjectCompiler, is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ figment::{ @@ -86,6 +86,13 @@ pub struct RunArgs { /// Enables Alphanet features. #[arg(long)] pub alphanet: bool, + + /// If generate a file with the signatures of the functions and events of the project. + /// The file will be saved in the foundry cache directory. + /// + /// default value: false + #[arg(long, short = 'c', visible_alias = "cls")] + pub cache_local_signatures: bool, } impl RunArgs { @@ -242,6 +249,18 @@ impl RunArgs { } }; + if self.cache_local_signatures { + let project = config.project()?; + let compiler = ProjectCompiler::new().quiet(true); + let output = compiler.compile(&project)?; + if let Err(err) = cache_local_signatures(&output, Config::foundry_cache_dir().unwrap()) + { + warn!(target: "cast::run", ?err, "failed to flush signature cache"); + } else { + trace!(target: "cast::run", "flushed signature cache") + } + } + handle_traces( result, &config, diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index ebb4da9f1d45..275cab4d43a0 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -140,6 +140,14 @@ pub struct CoreBuildArgs { #[command(flatten)] #[serde(flatten)] pub project_paths: ProjectPathsArgs, + + /// If generate a file with the signatures of the functions and events of the project. + /// The file will be saved in the foundry cache directory. + /// + /// default value: false + #[arg(long, visible_alias = "cls")] + #[serde(skip)] + pub cache_local_signatures: bool, } impl CoreBuildArgs { diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 8a4bff729410..be7c10b40866 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -16,7 +16,7 @@ use foundry_evm::{ traces::{ debug::DebugTraceIdentifier, decode_trace_arena, - identifier::{EtherscanIdentifier, SignaturesIdentifier}, + identifier::{CachedSignatures, EtherscanIdentifier, SignaturesIdentifier}, render_trace_arena_with_bytecodes, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, }, @@ -441,3 +441,25 @@ pub async fn print_traces( println!("Gas used: {}", result.gas_used); Ok(()) } + +/// Traverse the artifacts in the project to generate local signatures and merge them into the cache +/// file. +pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_path: PathBuf) -> Result<()> { + let path = cache_path.join("signatures"); + let mut cached_signatures = CachedSignatures::load(cache_path); + output.artifacts().for_each(|(_, artifact)| { + if let Some(abi) = &artifact.abi { + for func in abi.functions() { + cached_signatures.functions.insert(func.selector().to_string(), func.signature()); + } + for event in abi.events() { + cached_signatures + .events + .insert(event.selector().to_string(), event.full_signature()); + } + } + }); + + fs::write_json_file(&path, &cached_signatures)?; + Ok(()) +} diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs index a16b108d8537..768916796641 100644 --- a/crates/evm/traces/src/identifier/mod.rs +++ b/crates/evm/traces/src/identifier/mod.rs @@ -12,7 +12,7 @@ mod etherscan; pub use etherscan::EtherscanIdentifier; mod signatures; -pub use signatures::{SignaturesIdentifier, SingleSignaturesIdentifier}; +pub use signatures::{CachedSignatures, SignaturesIdentifier, SingleSignaturesIdentifier}; /// An address identity pub struct AddressIdentity<'a> { diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs index 2d7cadad1b82..0169b5c9a04a 100644 --- a/crates/evm/traces/src/identifier/signatures.rs +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -16,11 +16,29 @@ use tokio::sync::RwLock; pub type SingleSignaturesIdentifier = Arc>; #[derive(Debug, Default, Serialize, Deserialize)] -struct CachedSignatures { - events: BTreeMap, - functions: BTreeMap, +pub struct CachedSignatures { + pub events: BTreeMap, + pub functions: BTreeMap, } +impl CachedSignatures { + #[instrument(target = "evm::traces")] + pub fn load(cache_path: PathBuf) -> Self { + let path = cache_path.join("signatures"); + if path.is_file() { + fs::read_json_file(&path) + .map_err( + |err| warn!(target: "evm::traces", ?path, ?err, "failed to read cache file"), + ) + .unwrap_or_default() + } else { + if let Err(err) = std::fs::create_dir_all(cache_path) { + warn!(target: "evm::traces", "could not create signatures cache dir: {:?}", err); + } + Self::default() + } + } +} /// An identifier that tries to identify functions and events using signatures found at /// `https://openchain.xyz` or a local cache. #[derive(Debug)] @@ -46,16 +64,7 @@ impl SignaturesIdentifier { let identifier = if let Some(cache_path) = cache_path { let path = cache_path.join("signatures"); trace!(target: "evm::traces", ?path, "reading signature cache"); - let cached = if path.is_file() { - fs::read_json_file(&path) - .map_err(|err| warn!(target: "evm::traces", ?path, ?err, "failed to read cache file")) - .unwrap_or_default() - } else { - if let Err(err) = std::fs::create_dir_all(cache_path) { - warn!(target: "evm::traces", "could not create signatures cache dir: {:?}", err); - } - CachedSignatures::default() - }; + let cached = CachedSignatures::load(cache_path); Self { cached, cached_path: Some(path), unavailable: HashSet::default(), client } } else { Self { diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index 53bc5bc2001b..c9b38e9c3993 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -1,7 +1,10 @@ use super::{install, watch::WatchArgs}; use clap::Parser; use eyre::Result; -use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; +use foundry_cli::{ + opts::CoreBuildArgs, + utils::{cache_local_signatures, LoadConfig}, +}; use foundry_common::compile::ProjectCompiler; use foundry_compilers::{ compilers::{multi::MultiCompilerLanguage, Language}, @@ -111,6 +114,15 @@ impl BuildArgs { println!("{}", serde_json::to_string_pretty(&output.output())?); } + if self.args.cache_local_signatures { + if let Err(err) = cache_local_signatures(&output, Config::foundry_cache_dir().unwrap()) + { + warn!(target: "forge::build", ?err, "failed to flush signature cache"); + } else { + trace!(target: "forge::build", "flushed signature cache") + } + } + Ok(output) }