From 267d9d02431b57e9511ffc2c73fdd9c1b269b7a0 Mon Sep 17 00:00:00 2001 From: Silver Date: Thu, 25 Sep 2025 17:41:27 +0800 Subject: [PATCH 1/8] Add Sourcify support to TraceIdentifiers --- Cargo.lock | 1 + crates/chisel/src/dispatcher.rs | 2 +- crates/evm/traces/Cargo.toml | 1 + crates/evm/traces/src/identifier/mod.rs | 18 +++++- crates/evm/traces/src/identifier/sourcify.rs | 67 ++++++++++++++++++++ crates/forge/src/cmd/test/mod.rs | 6 +- crates/script/src/execute.rs | 9 +-- 7 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 crates/evm/traces/src/identifier/sourcify.rs diff --git a/Cargo.lock b/Cargo.lock index 331f1b62b285e..67127cd50e156 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4813,6 +4813,7 @@ dependencies = [ "itertools 0.14.0", "memchr", "rayon", + "reqwest", "revm", "revm-inspectors", "serde", diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index a7fbcd335e9fd..114b634913d37 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -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, )?; diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 527ea422bbd07..d2404bd8027d2 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -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"] } diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs index 8ad929b7317a0..007d7bc58965e 100644 --- a/crates/evm/traces/src/identifier/mod.rs +++ b/crates/evm/traces/src/identifier/mod.rs @@ -12,6 +12,9 @@ pub use local::LocalTraceIdentifier; mod etherscan; pub use etherscan::EtherscanIdentifier; +mod sourcify; +pub use sourcify::SourceifyIdentifier; + mod signatures; pub use signatures::{SignaturesCache, SignaturesIdentifier}; @@ -43,6 +46,8 @@ pub struct TraceIdentifiers<'a> { pub local: Option>, /// The optional Etherscan trace identifier. pub etherscan: Option, + /// The optional Sourcify trace identifier. + pub sourcify: Option, } impl Default for TraceIdentifiers<'_> { @@ -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)); } @@ -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. @@ -96,8 +104,14 @@ impl<'a> TraceIdentifiers<'a> { Ok(self) } + /// Sets the sourcify identifier. + pub fn with_sourcify(mut self) -> Self { + self.sourcify = Some(SourceifyIdentifier::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() } } diff --git a/crates/evm/traces/src/identifier/sourcify.rs b/crates/evm/traces/src/identifier/sourcify.rs new file mode 100644 index 0000000000000..e523588be262c --- /dev/null +++ b/crates/evm/traces/src/identifier/sourcify.rs @@ -0,0 +1,67 @@ +use super::{IdentifiedAddress, TraceIdentifier}; +use alloy_json_abi::JsonAbi; +use revm_inspectors::tracing::types::CallTraceNode; +use serde::Deserialize; +use std::borrow::Cow; + +#[derive(Deserialize)] +struct SourceifyFile { + name: String, + content: String, +} + +/// A trace identifier that uses Sourcify to identify contract ABIs. +pub struct SourceifyIdentifier; + +impl SourceifyIdentifier { + pub fn new() -> Self { + Self + } +} + +impl Default for SourceifyIdentifier { + fn default() -> Self { + Self::new() + } +} + +impl TraceIdentifier for SourceifyIdentifier { + fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { + let mut identities = Vec::new(); + + for &node in nodes { + let address = node.trace.address; + + // Try to get ABI from Sourcify + let abi = foundry_common::block_on(async { + let client = reqwest::Client::new(); + let url = format!("https://repo.sourcify.dev/contracts/full_match/1/{address:?}/"); + + let files: Vec = + client.get(&url).send().await.ok()?.json().await.ok()?; + + for file in files { + if file.name == "metadata.json" { + let metadata: serde_json::Value = + serde_json::from_str(&file.content).ok()?; + let abi_value = metadata.get("output")?.get("abi")?; + return serde_json::from_value::(abi_value.clone()).ok(); + } + } + None + }); + + 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 + } +} diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index 7c8a47da1bad9..26baca63292bb 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -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. diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 06a665b86823a..3ae2cf9d037ea 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -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); From 34df55b98a1dc623d9a2b4d80b87b7a609fa5923 Mon Sep 17 00:00:00 2001 From: Silver Date: Thu, 25 Sep 2025 18:30:37 +0800 Subject: [PATCH 2/8] simply code --- crates/evm/traces/src/identifier/sourcify.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/evm/traces/src/identifier/sourcify.rs b/crates/evm/traces/src/identifier/sourcify.rs index e523588be262c..ffcb13ba56a6b 100644 --- a/crates/evm/traces/src/identifier/sourcify.rs +++ b/crates/evm/traces/src/identifier/sourcify.rs @@ -28,27 +28,23 @@ impl Default for SourceifyIdentifier { impl TraceIdentifier for SourceifyIdentifier { fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { let mut identities = Vec::new(); + let client = reqwest::Client::new(); for &node in nodes { let address = node.trace.address; // Try to get ABI from Sourcify let abi = foundry_common::block_on(async { - let client = reqwest::Client::new(); let url = format!("https://repo.sourcify.dev/contracts/full_match/1/{address:?}/"); let files: Vec = client.get(&url).send().await.ok()?.json().await.ok()?; - for file in files { - if file.name == "metadata.json" { - let metadata: serde_json::Value = - serde_json::from_str(&file.content).ok()?; - let abi_value = metadata.get("output")?.get("abi")?; - return serde_json::from_value::(abi_value.clone()).ok(); - } - } - None + let metadata_file = files.into_iter().find(|file| file.name == "metadata.json")?; + let metadata: serde_json::Value = + serde_json::from_str(&metadata_file.content).ok()?; + let abi_value = metadata.get("output")?.get("abi")?; + serde_json::from_value::(abi_value.clone()).ok() }); if let Some(abi) = abi { From 252525816827c08e8efee3b9c80399f25ac7676d Mon Sep 17 00:00:00 2001 From: Silver Date: Thu, 25 Sep 2025 18:37:37 +0800 Subject: [PATCH 3/8] fix typos --- crates/evm/traces/src/identifier/mod.rs | 6 +++--- crates/evm/traces/src/identifier/sourcify.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs index 007d7bc58965e..154426202373d 100644 --- a/crates/evm/traces/src/identifier/mod.rs +++ b/crates/evm/traces/src/identifier/mod.rs @@ -13,7 +13,7 @@ mod etherscan; pub use etherscan::EtherscanIdentifier; mod sourcify; -pub use sourcify::SourceifyIdentifier; +pub use sourcify::SourcifyIdentifier; mod signatures; pub use signatures::{SignaturesCache, SignaturesIdentifier}; @@ -47,7 +47,7 @@ pub struct TraceIdentifiers<'a> { /// The optional Etherscan trace identifier. pub etherscan: Option, /// The optional Sourcify trace identifier. - pub sourcify: Option, + pub sourcify: Option, } impl Default for TraceIdentifiers<'_> { @@ -106,7 +106,7 @@ impl<'a> TraceIdentifiers<'a> { /// Sets the sourcify identifier. pub fn with_sourcify(mut self) -> Self { - self.sourcify = Some(SourceifyIdentifier::new()); + self.sourcify = Some(SourcifyIdentifier::new()); self } diff --git a/crates/evm/traces/src/identifier/sourcify.rs b/crates/evm/traces/src/identifier/sourcify.rs index ffcb13ba56a6b..bd98d2ef130e6 100644 --- a/crates/evm/traces/src/identifier/sourcify.rs +++ b/crates/evm/traces/src/identifier/sourcify.rs @@ -5,27 +5,27 @@ use serde::Deserialize; use std::borrow::Cow; #[derive(Deserialize)] -struct SourceifyFile { +struct SourcifyFile { name: String, content: String, } /// A trace identifier that uses Sourcify to identify contract ABIs. -pub struct SourceifyIdentifier; +pub struct SourcifyIdentifier; -impl SourceifyIdentifier { +impl SourcifyIdentifier { pub fn new() -> Self { Self } } -impl Default for SourceifyIdentifier { +impl Default for SourcifyIdentifier { fn default() -> Self { Self::new() } } -impl TraceIdentifier for SourceifyIdentifier { +impl TraceIdentifier for SourcifyIdentifier { fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { let mut identities = Vec::new(); let client = reqwest::Client::new(); @@ -37,7 +37,7 @@ impl TraceIdentifier for SourceifyIdentifier { let abi = foundry_common::block_on(async { let url = format!("https://repo.sourcify.dev/contracts/full_match/1/{address:?}/"); - let files: Vec = + let files: Vec = client.get(&url).send().await.ok()?.json().await.ok()?; let metadata_file = files.into_iter().find(|file| file.name == "metadata.json")?; From ed52eb6958e4ee3cfa76b6cd7bd48268c138160e Mon Sep 17 00:00:00 2001 From: Silver Date: Thu, 25 Sep 2025 18:50:02 +0800 Subject: [PATCH 4/8] add test --- crates/evm/traces/src/identifier/sourcify.rs | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/crates/evm/traces/src/identifier/sourcify.rs b/crates/evm/traces/src/identifier/sourcify.rs index bd98d2ef130e6..744289fb9fdb4 100644 --- a/crates/evm/traces/src/identifier/sourcify.rs +++ b/crates/evm/traces/src/identifier/sourcify.rs @@ -61,3 +61,39 @@ impl TraceIdentifier for SourcifyIdentifier { identities } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sourcify_identifier_creation() { + let identifier = SourcifyIdentifier::new(); + assert!(std::ptr::eq(&identifier, &identifier)); + } + + #[test] + fn test_sourcify_identifier_default() { + let identifier = SourcifyIdentifier::default(); + assert!(std::ptr::eq(&identifier, &identifier)); + } + + #[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_file_deserialization() { + let json = r#"{"name": "metadata.json", "content": "{\"output\": {\"abi\": []}}"}"#; + let file: Result = serde_json::from_str(json); + assert!(file.is_ok()); + + let file = file.unwrap(); + assert_eq!(file.name, "metadata.json"); + assert!(file.content.contains("abi")); + } +} From dcaff0e271a5cbbd095c993f44ee52ba2c9fcbc1 Mon Sep 17 00:00:00 2001 From: Silver Date: Thu, 25 Sep 2025 22:36:37 +0800 Subject: [PATCH 5/8] fix test and ci --- crates/evm/traces/src/identifier/sourcify.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/evm/traces/src/identifier/sourcify.rs b/crates/evm/traces/src/identifier/sourcify.rs index 744289fb9fdb4..d0471e87b1181 100644 --- a/crates/evm/traces/src/identifier/sourcify.rs +++ b/crates/evm/traces/src/identifier/sourcify.rs @@ -28,6 +28,12 @@ impl Default for SourcifyIdentifier { impl TraceIdentifier for SourcifyIdentifier { fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { let mut identities = Vec::new(); + + // Skip network requests in test environment to avoid CI hangs + if cfg!(test) { + return identities; + } + let client = reqwest::Client::new(); for &node in nodes { @@ -68,14 +74,14 @@ mod tests { #[test] fn test_sourcify_identifier_creation() { - let identifier = SourcifyIdentifier::new(); - assert!(std::ptr::eq(&identifier, &identifier)); + let _identifier = SourcifyIdentifier::new(); + // Test that creation doesn't panic } #[test] fn test_sourcify_identifier_default() { - let identifier = SourcifyIdentifier::default(); - assert!(std::ptr::eq(&identifier, &identifier)); + let _identifier = SourcifyIdentifier::new(); // Use new() instead of default() for unit structs + // Test that creation doesn't panic } #[test] From b3602fbbf8bcd18a74d4ee10158d2b859fbc065b Mon Sep 17 00:00:00 2001 From: Silver Date: Fri, 26 Sep 2025 01:10:29 +0800 Subject: [PATCH 6/8] Refactor to fix ci --- crates/evm/traces/src/identifier/sourcify.rs | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/evm/traces/src/identifier/sourcify.rs b/crates/evm/traces/src/identifier/sourcify.rs index d0471e87b1181..91f8b973b369a 100644 --- a/crates/evm/traces/src/identifier/sourcify.rs +++ b/crates/evm/traces/src/identifier/sourcify.rs @@ -28,13 +28,10 @@ impl Default for SourcifyIdentifier { impl TraceIdentifier for SourcifyIdentifier { fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec> { let mut identities = Vec::new(); - - // Skip network requests in test environment to avoid CI hangs - if cfg!(test) { - return identities; - } - - let client = reqwest::Client::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; @@ -43,14 +40,17 @@ impl TraceIdentifier for SourcifyIdentifier { let abi = foundry_common::block_on(async { let url = format!("https://repo.sourcify.dev/contracts/full_match/1/{address:?}/"); - let files: Vec = - client.get(&url).send().await.ok()?.json().await.ok()?; - - let metadata_file = files.into_iter().find(|file| file.name == "metadata.json")?; - let metadata: serde_json::Value = - serde_json::from_str(&metadata_file.content).ok()?; - let abi_value = metadata.get("output")?.get("abi")?; - serde_json::from_value::(abi_value.clone()).ok() + if let Ok(response) = client.get(&url).send().await + && let Ok(files) = response.json::>().await + { + let metadata_file = + files.into_iter().find(|file| file.name == "metadata.json")?; + let metadata: serde_json::Value = + serde_json::from_str(&metadata_file.content).ok()?; + let abi_value = metadata.get("output")?.get("abi")?; + return serde_json::from_value::(abi_value.clone()).ok(); + } + None }); if let Some(abi) = abi { From d1144b7717b5e88bf3e7304ce74694dbe810504e Mon Sep 17 00:00:00 2001 From: Silver Date: Tue, 30 Sep 2025 04:48:07 +0800 Subject: [PATCH 7/8] using APIv2 & replace test --- crates/evm/traces/src/identifier/sourcify.rs | 59 ++++++++++---------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/crates/evm/traces/src/identifier/sourcify.rs b/crates/evm/traces/src/identifier/sourcify.rs index 91f8b973b369a..0d8eae3b7f53b 100644 --- a/crates/evm/traces/src/identifier/sourcify.rs +++ b/crates/evm/traces/src/identifier/sourcify.rs @@ -1,15 +1,8 @@ use super::{IdentifiedAddress, TraceIdentifier}; use alloy_json_abi::JsonAbi; use revm_inspectors::tracing::types::CallTraceNode; -use serde::Deserialize; use std::borrow::Cow; -#[derive(Deserialize)] -struct SourcifyFile { - name: String, - content: String, -} - /// A trace identifier that uses Sourcify to identify contract ABIs. pub struct SourcifyIdentifier; @@ -36,21 +29,15 @@ impl TraceIdentifier for SourcifyIdentifier { for &node in nodes { let address = node.trace.address; - // Try to get ABI from Sourcify + // Try to get ABI from Sourcify using APIv2 let abi = foundry_common::block_on(async { - let url = format!("https://repo.sourcify.dev/contracts/full_match/1/{address:?}/"); - - if let Ok(response) = client.get(&url).send().await - && let Ok(files) = response.json::>().await - { - let metadata_file = - files.into_iter().find(|file| file.name == "metadata.json")?; - let metadata: serde_json::Value = - serde_json::from_str(&metadata_file.content).ok()?; - let abi_value = metadata.get("output")?.get("abi")?; - return serde_json::from_value::(abi_value.clone()).ok(); - } - None + 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::(abi_value.clone()).ok() }); if let Some(abi) = abi { @@ -93,13 +80,27 @@ mod tests { } #[test] - fn test_sourcify_file_deserialization() { - let json = r#"{"name": "metadata.json", "content": "{\"output\": {\"abi\": []}}"}"#; - let file: Result = serde_json::from_str(json); - assert!(file.is_ok()); - - let file = file.unwrap(); - assert_eq!(file.name, "metadata.json"); - assert!(file.content.contains("abi")); + 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 = serde_json::from_value(abi_value.clone()); + + assert!(abi.is_ok()); + let abi = abi.unwrap(); + assert_eq!(abi.len(), 1); } } From c89fde135e3fd0508eacd4502fd3a29b973e3ccb Mon Sep 17 00:00:00 2001 From: Silver Date: Wed, 1 Oct 2025 03:04:16 +0800 Subject: [PATCH 8/8] make chain id configurable --- crates/chisel/src/dispatcher.rs | 8 ++--- crates/evm/traces/src/identifier/mod.rs | 4 +-- crates/evm/traces/src/identifier/sourcify.rs | 36 ++++++++++++++------ crates/forge/src/cmd/test/mod.rs | 4 ++- crates/script/src/execute.rs | 10 +++--- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 114b634913d37..7fa3bfb884913 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -181,10 +181,10 @@ impl ChiselDispatcher { )?) .build(); - let mut identifier = TraceIdentifiers::new().with_sourcify().with_etherscan( - &session_config.foundry_config, - session_config.evm_opts.get_remote_chain_id().await, - )?; + let remote_chain_id = session_config.evm_opts.get_remote_chain_id().await; + let mut identifier = TraceIdentifiers::new() + .with_sourcify(remote_chain_id) + .with_etherscan(&session_config.foundry_config, remote_chain_id)?; if !identifier.is_empty() { for (_, trace) in &mut result.traces { decoder.identify(trace, &mut identifier); diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs index 154426202373d..a861d93cfa41a 100644 --- a/crates/evm/traces/src/identifier/mod.rs +++ b/crates/evm/traces/src/identifier/mod.rs @@ -105,8 +105,8 @@ impl<'a> TraceIdentifiers<'a> { } /// Sets the sourcify identifier. - pub fn with_sourcify(mut self) -> Self { - self.sourcify = Some(SourcifyIdentifier::new()); + pub fn with_sourcify(mut self, chain: Option) -> Self { + self.sourcify = Some(SourcifyIdentifier::new(chain)); self } diff --git a/crates/evm/traces/src/identifier/sourcify.rs b/crates/evm/traces/src/identifier/sourcify.rs index 0d8eae3b7f53b..e6516b03698a9 100644 --- a/crates/evm/traces/src/identifier/sourcify.rs +++ b/crates/evm/traces/src/identifier/sourcify.rs @@ -1,20 +1,25 @@ use super::{IdentifiedAddress, TraceIdentifier}; use alloy_json_abi::JsonAbi; +use foundry_config::Chain; use revm_inspectors::tracing::types::CallTraceNode; use std::borrow::Cow; /// A trace identifier that uses Sourcify to identify contract ABIs. -pub struct SourcifyIdentifier; +pub struct SourcifyIdentifier { + chain_id: u64, +} impl SourcifyIdentifier { - pub fn new() -> Self { - Self + /// Creates a new Sourcify identifier for the given chain. + pub fn new(chain: Option) -> Self { + let chain_id = chain.map(|c| c.id()).unwrap_or(1); + Self { chain_id } } } impl Default for SourcifyIdentifier { fn default() -> Self { - Self::new() + Self::new(None) } } @@ -31,8 +36,10 @@ impl TraceIdentifier for SourcifyIdentifier { // 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 url = format!( + "https://sourcify.dev/server/v2/contract/{}/{}?fields=abi", + self.chain_id, address + ); let response = client.get(&url).send().await.ok()?; let json: serde_json::Value = response.json().await.ok()?; @@ -58,22 +65,29 @@ impl TraceIdentifier for SourcifyIdentifier { #[cfg(test)] mod tests { use super::*; + use foundry_config::NamedChain; #[test] fn test_sourcify_identifier_creation() { - let _identifier = SourcifyIdentifier::new(); - // Test that creation doesn't panic + let identifier = SourcifyIdentifier::new(None); + assert_eq!(identifier.chain_id, 1); // Default to mainnet + } + + #[test] + fn test_sourcify_identifier_with_chain() { + let identifier = SourcifyIdentifier::new(Some(NamedChain::Polygon.into())); + assert_eq!(identifier.chain_id, 137); // Polygon chain ID } #[test] fn test_sourcify_identifier_default() { - let _identifier = SourcifyIdentifier::new(); // Use new() instead of default() for unit structs - // Test that creation doesn't panic + let identifier = SourcifyIdentifier::default(); + assert_eq!(identifier.chain_id, 1); // Default to mainnet } #[test] fn test_empty_nodes() { - let mut identifier = SourcifyIdentifier::new(); + let mut identifier = SourcifyIdentifier::default(); let nodes: Vec<&CallTraceNode> = vec![]; let result = identifier.identify_addresses(&nodes); assert!(result.is_empty()); diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index 26baca63292bb..5e4700f32ec4c 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -528,7 +528,9 @@ impl TestArgs { // 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_sourcify().with_etherscan(&config, remote_chain_id)?; + identifier = identifier + .with_sourcify(remote_chain_id) + .with_etherscan(&config, remote_chain_id)?; } // Build the trace decoder. diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 3ae2cf9d037ea..7bde465f77e03 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -335,11 +335,11 @@ impl ExecutedState { .with_label_disabled(self.args.disable_labels) .build(); - 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, - )?; + let remote_chain_id = self.script_config.evm_opts.get_remote_chain_id().await; + let mut identifier = TraceIdentifiers::new() + .with_local(known_contracts) + .with_sourcify(remote_chain_id) + .with_etherscan(&self.script_config.config, remote_chain_id)?; for (_, trace) in &self.execution_result.traces { decoder.identify(trace, &mut identifier);