Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/chisel/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl ChiselDispatcher {
)?)
.build();

let mut identifier = TraceIdentifiers::new().with_etherscan(
let mut identifier = TraceIdentifiers::new().with_sourcify().with_etherscan(
&session_config.foundry_config,
session_config.evm_opts.get_remote_chain_id().await,
)?;
Expand Down
1 change: 1 addition & 0 deletions crates/evm/traces/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ revm-inspectors.workspace = true
eyre.workspace = true
futures.workspace = true
itertools.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio = { workspace = true, features = ["time", "macros"] }
Expand Down
18 changes: 16 additions & 2 deletions crates/evm/traces/src/identifier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub use local::LocalTraceIdentifier;
mod etherscan;
pub use etherscan::EtherscanIdentifier;

mod sourcify;
pub use sourcify::SourcifyIdentifier;

mod signatures;
pub use signatures::{SignaturesCache, SignaturesIdentifier};

Expand Down Expand Up @@ -43,6 +46,8 @@ pub struct TraceIdentifiers<'a> {
pub local: Option<LocalTraceIdentifier<'a>>,
/// The optional Etherscan trace identifier.
pub etherscan: Option<EtherscanIdentifier>,
/// The optional Sourcify trace identifier.
pub sourcify: Option<SourcifyIdentifier>,
}

impl Default for TraceIdentifiers<'_> {
Expand All @@ -60,6 +65,9 @@ impl TraceIdentifier for TraceIdentifiers<'_> {
return identities;
}
}
if let Some(sourcify) = &mut self.sourcify {
identities.extend(sourcify.identify_addresses(nodes));
}
if let Some(etherscan) = &mut self.etherscan {
identities.extend(etherscan.identify_addresses(nodes));
}
Expand All @@ -70,7 +78,7 @@ impl TraceIdentifier for TraceIdentifiers<'_> {
impl<'a> TraceIdentifiers<'a> {
/// Creates a new, empty instance.
pub const fn new() -> Self {
Self { local: None, etherscan: None }
Self { local: None, etherscan: None, sourcify: None }
}

/// Sets the local identifier.
Expand All @@ -96,8 +104,14 @@ impl<'a> TraceIdentifiers<'a> {
Ok(self)
}

/// Sets the sourcify identifier.
pub fn with_sourcify(mut self) -> Self {
self.sourcify = Some(SourcifyIdentifier::new());
self
}

/// Returns `true` if there are no set identifiers.
pub fn is_empty(&self) -> bool {
self.local.is_none() && self.etherscan.is_none()
self.local.is_none() && self.etherscan.is_none() && self.sourcify.is_none()
}
}
106 changes: 106 additions & 0 deletions crates/evm/traces/src/identifier/sourcify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use super::{IdentifiedAddress, TraceIdentifier};
use alloy_json_abi::JsonAbi;
use revm_inspectors::tracing::types::CallTraceNode;
use std::borrow::Cow;

/// A trace identifier that uses Sourcify to identify contract ABIs.
pub struct SourcifyIdentifier;

impl SourcifyIdentifier {
pub fn new() -> Self {
Self
}
}

impl Default for SourcifyIdentifier {
fn default() -> Self {
Self::new()
}
}

impl TraceIdentifier for SourcifyIdentifier {
fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec<IdentifiedAddress<'_>> {
let mut identities = Vec::new();
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.expect("Failed to create HTTP client");

for &node in nodes {
let address = node.trace.address;

// Try to get ABI from Sourcify using APIv2
let abi = foundry_common::block_on(async {
let url =
format!("https://sourcify.dev/server/v2/contract/1/{address}?fields=abi");

let response = client.get(&url).send().await.ok()?;
let json: serde_json::Value = response.json().await.ok()?;
let abi_value = json.get("abi")?;
serde_json::from_value::<JsonAbi>(abi_value.clone()).ok()
});

if let Some(abi) = abi {
identities.push(IdentifiedAddress {
address,
label: Some("Sourcify".to_string()),
contract: Some("Sourcify".to_string()),
abi: Some(Cow::Owned(abi)),
artifact_id: None,
});
}
}

identities
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_sourcify_identifier_creation() {
let _identifier = SourcifyIdentifier::new();
// Test that creation doesn't panic
}

#[test]
fn test_sourcify_identifier_default() {
let _identifier = SourcifyIdentifier::new(); // Use new() instead of default() for unit structs
// Test that creation doesn't panic
}

#[test]
fn test_empty_nodes() {
let mut identifier = SourcifyIdentifier::new();
let nodes: Vec<&CallTraceNode> = vec![];
let result = identifier.identify_addresses(&nodes);
assert!(result.is_empty());
}

#[test]
fn test_sourcify_apiv2_response_parsing() {
// Test that we can parse the new APIv2 response format correctly
let response_json = r#"{
"abi": [
{"name": "transfer", "type": "function", "inputs": [], "outputs": []}
],
"matchId": "1532018",
"creationMatch": "match",
"runtimeMatch": "match",
"verifiedAt": "2024-08-08T13:20:07Z",
"match": "match",
"chainId": "1",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}"#;

let json: serde_json::Value = serde_json::from_str(response_json).unwrap();
let abi_value = json.get("abi").unwrap();
let abi: Result<JsonAbi, _> = serde_json::from_value(abi_value.clone());

assert!(abi.is_ok());
let abi = abi.unwrap();
assert_eq!(abi.len(), 1);
}
}
6 changes: 3 additions & 3 deletions crates/forge/src/cmd/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,10 +525,10 @@ impl TestArgs {
// Set up trace identifiers.
let mut identifier = TraceIdentifiers::new().with_local(&known_contracts);

// Avoid using etherscan for gas report as we decode more traces and this will be
// expensive.
// Avoid using etherscan and sourcify for gas report as we decode more traces and this will
// be expensive.
if !self.gas_report {
identifier = identifier.with_etherscan(&config, remote_chain_id)?;
identifier = identifier.with_sourcify().with_etherscan(&config, remote_chain_id)?;
}

// Build the trace decoder.
Expand Down
9 changes: 5 additions & 4 deletions crates/script/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,11 @@ impl ExecutedState {
.with_label_disabled(self.args.disable_labels)
.build();

let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_etherscan(
&self.script_config.config,
self.script_config.evm_opts.get_remote_chain_id().await,
)?;
let mut identifier =
TraceIdentifiers::new().with_local(known_contracts).with_sourcify().with_etherscan(
&self.script_config.config,
self.script_config.evm_opts.get_remote_chain_id().await,
)?;

for (_, trace) in &self.execution_result.traces {
decoder.identify(trace, &mut identifier);
Expand Down
Loading