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

perf: optimize trace identifiers #5680

Merged
merged 1 commit into from
Aug 21, 2023
Merged
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
14 changes: 7 additions & 7 deletions crates/chisel/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -958,13 +958,13 @@ impl ChiselDispatcher {
session_config.evm_opts.get_remote_chain_id(),
)?;

let mut decoder =
CallTraceDecoderBuilder::new().with_labels(result.labeled_addresses.clone()).build();

decoder.add_signature_identifier(SignaturesIdentifier::new(
Config::foundry_cache_dir(),
session_config.foundry_config.offline,
)?);
let mut decoder = CallTraceDecoderBuilder::new()
.with_labels(result.labeled_addresses.iter().map(|(a, s)| (*a, s.clone())))
.with_signature_identifier(SignaturesIdentifier::new(
Config::foundry_cache_dir(),
session_config.foundry_config.offline,
)?)
.build();

for (_, trace) in &mut result.traces {
// decoder.identify(trace, &mut local_identifier);
Expand Down
13 changes: 7 additions & 6 deletions crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,13 @@ pub async fn handle_traces(
None
});

let mut decoder = CallTraceDecoderBuilder::new().with_labels(labeled_addresses).build();

decoder.add_signature_identifier(SignaturesIdentifier::new(
Config::foundry_cache_dir(),
config.offline,
)?);
let mut decoder = CallTraceDecoderBuilder::new()
.with_labels(labeled_addresses)
.with_signature_identifier(SignaturesIdentifier::new(
Config::foundry_cache_dir(),
config.offline,
)?)
.build();

for (_, trace) in &mut result.traces {
decoder.identify(trace, &mut etherscan_identifier);
Expand Down
74 changes: 46 additions & 28 deletions crates/evm/src/trace/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{
identifier::{SingleSignaturesIdentifier, TraceIdentifier},
identifier::{AddressIdentity, SingleSignaturesIdentifier, TraceIdentifier},
CallTraceArena, RawOrDecodedCall, RawOrDecodedLog, RawOrDecodedReturnData,
};
use crate::{
Expand All @@ -20,22 +20,27 @@ use std::collections::{BTreeMap, HashMap};

/// Build a new [CallTraceDecoder].
#[derive(Default)]
#[must_use = "builders do nothing unless you call `build` on them"]
pub struct CallTraceDecoderBuilder {
decoder: CallTraceDecoder,
}

impl CallTraceDecoderBuilder {
/// Create a new builder.
#[inline]
pub fn new() -> Self {
Self { decoder: CallTraceDecoder::new().clone() }
}

/// Add known labels to the decoder.
#[inline]
pub fn with_labels(mut self, labels: impl IntoIterator<Item = (Address, String)>) -> Self {
self.decoder.labels.extend(labels);
self
}

/// Add known events to the decoder.
#[inline]
pub fn with_events(mut self, events: impl IntoIterator<Item = Event>) -> Self {
for event in events {
self.decoder
Expand All @@ -48,12 +53,21 @@ impl CallTraceDecoderBuilder {
}

/// Sets the verbosity level of the decoder.
#[inline]
pub fn with_verbosity(mut self, level: u8) -> Self {
self.decoder.verbosity = level;
self
}

/// Sets the signature identifier for events and functions.
#[inline]
pub fn with_signature_identifier(mut self, identifier: SingleSignaturesIdentifier) -> Self {
self.decoder.signature_identifier = Some(identifier);
self
}

/// Build the decoder.
#[inline]
pub fn build(self) -> CallTraceDecoder {
self.decoder
}
Expand Down Expand Up @@ -168,23 +182,26 @@ impl CallTraceDecoder {
}
}

pub fn add_signature_identifier(&mut self, identifier: SingleSignaturesIdentifier) {
self.signature_identifier = Some(identifier);
}

/// Identify unknown addresses in the specified call trace using the specified identifier.
///
/// Unknown contracts are contracts that either lack a label or an ABI.
#[inline]
pub fn identify(&mut self, trace: &CallTraceArena, identifier: &mut impl TraceIdentifier) {
let unidentified_addresses = trace
.addresses()
.into_iter()
.filter(|(address, _)| {
!self.labels.contains_key(address) || !self.contracts.contains_key(address)
})
.collect();

identifier.identify_addresses(unidentified_addresses).iter().for_each(|identity| {
self.collect_identities(identifier.identify_addresses(self.addresses(trace)));
}

#[inline(always)]
fn addresses<'a>(
&'a self,
trace: &'a CallTraceArena,
) -> impl Iterator<Item = (&'a Address, Option<&'a [u8]>)> + 'a {
trace.addresses().into_iter().filter(|&(address, _)| {
!self.labels.contains_key(address) || !self.contracts.contains_key(address)
})
}

fn collect_identities(&mut self, identities: Vec<AddressIdentity>) {
for identity in identities {
let address = identity.address;

if let Some(contract) = &identity.contract {
Expand All @@ -197,30 +214,31 @@ impl CallTraceDecoder {

if let Some(abi) = &identity.abi {
// Store known functions for the address
abi.functions()
.map(|func| (func.short_signature(), func.clone()))
.for_each(|(sig, func)| self.functions.entry(sig).or_default().push(func));
for function in abi.functions() {
self.functions
.entry(function.short_signature())
.or_default()
.push(function.clone())
}

// Flatten events from all ABIs
abi.events()
.map(|event| ((event.signature(), indexed_inputs(event)), event.clone()))
.for_each(|(sig, event)| {
self.events.entry(sig).or_default().push(event);
});
for event in abi.events() {
let sig = (event.signature(), indexed_inputs(event));
self.events.entry(sig).or_default().push(event.clone());
}

// Flatten errors from all ABIs
abi.errors().for_each(|error| {
let entry = self.errors.errors.entry(error.name.clone()).or_default();
entry.push(error.clone());
});
for error in abi.errors() {
self.errors.errors.entry(error.name.clone()).or_default().push(error.clone());
}

self.receive_contracts.entry(address).or_insert(abi.receive);
}
});
}
}

pub async fn decode(&self, traces: &mut CallTraceArena) {
for node in traces.arena.iter_mut() {
for node in &mut traces.arena {
// Set contract name
if let Some(contract) = self.contracts.get(&node.trace.address).cloned() {
node.trace.contract = Some(contract);
Expand Down
10 changes: 5 additions & 5 deletions crates/evm/src/trace/identifier/etherscan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ impl EtherscanIdentifier {
}

impl TraceIdentifier for EtherscanIdentifier {
fn identify_addresses(
&mut self,
addresses: Vec<(&Address, Option<&[u8]>)>,
) -> Vec<AddressIdentity> {
trace!(target: "etherscanidentifier", "identify {} addresses", addresses.len());
fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec<AddressIdentity>
where
A: Iterator<Item = (&'a Address, Option<&'a [u8]>)>,
{
trace!(target: "etherscanidentifier", "identify {:?} addresses", addresses.size_hint().1);

let Some(client) = self.client.clone() else {
// no client was configured
Expand Down
49 changes: 19 additions & 30 deletions crates/evm/src/trace/identifier/local.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,45 @@
use super::{AddressIdentity, TraceIdentifier};
use ethers::{
abi::{Abi, Address, Event},
prelude::ArtifactId,
};
use ethers::abi::{Address, Event};
use foundry_common::contracts::{diff_score, ContractsByArtifact};
use itertools::Itertools;
use ordered_float::OrderedFloat;
use std::{borrow::Cow, collections::BTreeMap};
use std::borrow::Cow;

/// A trace identifier that tries to identify addresses using local contracts.
pub struct LocalTraceIdentifier {
local_contracts: BTreeMap<Vec<u8>, (ArtifactId, Abi)>,
pub struct LocalTraceIdentifier<'a> {
known_contracts: &'a ContractsByArtifact,
}

impl LocalTraceIdentifier {
pub fn new(known_contracts: &ContractsByArtifact) -> Self {
Self {
local_contracts: known_contracts
.iter()
.map(|(id, (abi, runtime_code))| (runtime_code.clone(), (id.clone(), abi.clone())))
.collect(),
}
impl<'a> LocalTraceIdentifier<'a> {
pub fn new(known_contracts: &'a ContractsByArtifact) -> Self {
Self { known_contracts }
Comment on lines -16 to +14
Copy link
Member Author

Choose a reason for hiding this comment

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

Remove useless re-allocations, we only need to be able to iterate over the 3 values (id, abi, code)

}

/// Get all the events of the local contracts.
pub fn events(&self) -> impl Iterator<Item = &Event> {
self.local_contracts.iter().flat_map(|(_, (_, abi))| abi.events())
self.known_contracts.iter().flat_map(|(_, (abi, _))| abi.events())
}
}

impl TraceIdentifier for LocalTraceIdentifier {
fn identify_addresses(
&mut self,
addresses: Vec<(&Address, Option<&[u8]>)>,
) -> Vec<AddressIdentity> {
impl TraceIdentifier for LocalTraceIdentifier<'_> {
fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec<AddressIdentity>
where
A: Iterator<Item = (&'a Address, Option<&'a [u8]>)>,
{
addresses
.into_iter()
.filter_map(|(address, code)| {
let code = code?;
let (_, (_, (id, abi))) = self
.local_contracts
let (_, id, abi) = self
.known_contracts
.iter()
.filter_map(|entry| {
let score = diff_score(entry.0, code);
.filter_map(|(id, (abi, known_code))| {
let score = diff_score(known_code, code);
if score < 0.1 {
Some((OrderedFloat(score), entry))
Some((OrderedFloat(score), id, abi))
} else {
None
}
})
.sorted_by_key(|(score, _)| *score)
.next()?;
.min_by_key(|(score, _, _)| *score)?;

Some(AddressIdentity {
address: *address,
Expand Down
8 changes: 3 additions & 5 deletions crates/evm/src/trace/identifier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ pub struct AddressIdentity<'a> {
pub trait TraceIdentifier {
// TODO: Update docs
/// Attempts to identify an address in one or more call traces.
#[allow(clippy::type_complexity)]
fn identify_addresses(
&mut self,
addresses: Vec<(&Address, Option<&[u8]>)>,
) -> Vec<AddressIdentity>;
fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec<AddressIdentity>
where
A: Iterator<Item = (&'a Address, Option<&'a [u8]>)>;
}
63 changes: 43 additions & 20 deletions crates/evm/src/trace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,13 +561,39 @@ impl fmt::Display for CallTrace {
}

/// Specifies the kind of trace.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TraceKind {
Deployment,
Setup,
Execution,
}

impl TraceKind {
/// Returns `true` if the trace kind is [`Deployment`].
///
/// [`Deployment`]: TraceKind::Deployment
#[must_use]
pub fn is_deployment(self) -> bool {
matches!(self, Self::Deployment)
}

/// Returns `true` if the trace kind is [`Setup`].
///
/// [`Setup`]: TraceKind::Setup
#[must_use]
pub fn is_setup(self) -> bool {
matches!(self, Self::Setup)
}

/// Returns `true` if the trace kind is [`Execution`].
///
/// [`Execution`]: TraceKind::Execution
#[must_use]
pub fn is_execution(self) -> bool {
matches!(self, Self::Execution)
}
}

/// Chooses the color of the trace depending on the destination address and status of the call.
fn trace_color(trace: &CallTrace) -> Color {
if trace.address == CHEATCODE_ADDRESS {
Expand All @@ -584,26 +610,23 @@ pub fn load_contracts(
traces: Traces,
known_contracts: Option<&ContractsByArtifact>,
) -> ContractsByAddress {
if let Some(contracts) = known_contracts {
let mut local_identifier = LocalTraceIdentifier::new(contracts);
let mut decoder = CallTraceDecoderBuilder::new().build();
for (_, trace) in &traces {
decoder.identify(trace, &mut local_identifier);
}

decoder
.contracts
.iter()
.filter_map(|(addr, name)| {
if let Ok(Some((_, (abi, _)))) = contracts.find_by_name_or_identifier(name) {
return Some((*addr, (name.clone(), abi.clone())))
}
None
})
.collect()
} else {
BTreeMap::new()
let Some(contracts) = known_contracts else { return BTreeMap::new() };
let mut local_identifier = LocalTraceIdentifier::new(contracts);
let mut decoder = CallTraceDecoderBuilder::new().build();
for (_, trace) in &traces {
decoder.identify(trace, &mut local_identifier);
}

decoder
.contracts
.iter()
.filter_map(|(addr, name)| {
if let Ok(Some((_, (abi, _)))) = contracts.find_by_name_or_identifier(name) {
return Some((*addr, (name.clone(), abi.clone())))
}
None
})
.collect()
}

/// creates the memory data in 32byte chunks
Expand Down
6 changes: 3 additions & 3 deletions crates/evm/src/trace/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub(crate) fn decode_cheatcode_inputs(
"serializeBytes32" |
"serializeString" |
"serializeBytes" => {
if verbosity == 5 {
if verbosity >= 5 {
Copy link
Member

Choose a reason for hiding this comment

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

nice catch!

None
} else {
let mut decoded = func.decode_input(&data[SELECTOR_LEN..]).ok()?;
Expand Down Expand Up @@ -111,10 +111,10 @@ pub(crate) fn decode_cheatcode_outputs(
// redacts derived private key
return Some("<pk>".to_string())
}
if func.name == "parseJson" && verbosity != 5 {
if func.name == "parseJson" && verbosity < 5 {
return Some("<encoded JSON value>".to_string())
}
if func.name == "readFile" && verbosity != 5 {
if func.name == "readFile" && verbosity < 5 {
return Some("<file>".to_string())
}
None
Expand Down
Loading
Loading