Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(forge, cast): add RunArgs generate_local_signatures to enable trace with local contracts functions and events #7359

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions crates/cast/bin/cmd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -242,6 +249,18 @@ impl RunArgs {
}
};

if self.cache_local_signatures {
let project = config.project()?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should print that we're compiling to get the signatures

let compiler = ProjectCompiler::new().quiet(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need to actually compile here, right?
@klkvr is there an easier way to just get the artifacts?

Copy link
Member

@klkvr klkvr Jul 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to compile as it might be run on a clean project. though I think we can only request abi here, should speed things up

if there are cached artifacts then compilation would just use them, so that's fine I believe

Copy link
Author

@byteshijinn byteshijinn Jul 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually users are more likely to trace their own project files and compile the whole project before deploying. But some one may dont want to run two command to generate signature cache with new file updates (may be an upgradable contract should try different events). If file not updated, compile will be complete fast.

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,
Expand Down
8 changes: 8 additions & 0 deletions crates/cli/src/opts/build/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 23 additions & 1 deletion crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -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(())
}
2 changes: 1 addition & 1 deletion crates/evm/traces/src/identifier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
35 changes: 22 additions & 13 deletions crates/evm/traces/src/identifier/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,29 @@ use tokio::sync::RwLock;
pub type SingleSignaturesIdentifier = Arc<RwLock<SignaturesIdentifier>>;

#[derive(Debug, Default, Serialize, Deserialize)]
struct CachedSignatures {
events: BTreeMap<String, String>,
functions: BTreeMap<String, String>,
pub struct CachedSignatures {
pub events: BTreeMap<String, String>,
pub functions: BTreeMap<String, String>,
}

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)]
Expand All @@ -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 {
Expand Down
14 changes: 13 additions & 1 deletion crates/forge/bin/cmd/build.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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)
}

Expand Down