Skip to content

Commit

Permalink
chore: delegate trace formatting to revm-inspectors
Browse files Browse the repository at this point in the history
Based on foundry-rs/foundry#8224

Co-authored-by: zerosnacks <[email protected]>
Co-authored-by: DaniPopes <[email protected]>
  • Loading branch information
3 people committed Oct 28, 2024
1 parent dd33f1d commit 1bbc7c2
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 421 deletions.
2 changes: 0 additions & 2 deletions Cargo.lock

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

8 changes: 4 additions & 4 deletions crates/edr_solidity_tests/src/gas_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,7 @@ impl GasReport {
return;
}

let decoded = decoder.decode_function(&node.trace).await;

let Some(name) = &decoded.contract else {
let Some(name) = decoder.contracts.get(&node.trace.address) else {
return;
};
let contract_name = name.rsplit(':').next().unwrap_or(name);
Expand All @@ -108,12 +106,14 @@ impl GasReport {
return;
}

let decoded = || decoder.decode_function(&node.trace);

let contract_info = self.contracts.entry(name.to_string()).or_default();
if trace.kind.is_any_create() {
trace!(contract_name, "adding create gas info");
contract_info.gas = trace.gas_used;
contract_info.size = trace.data.len();
} else if let Some(DecodedCallData { signature, .. }) = decoded.func {
} else if let Some(DecodedCallData { signature, .. }) = decoded().await.call_data {
let name = signature.split('(').next().unwrap();
// ignore any test/setup functions
let should_include = !(name.is_test() || name.is_invariant_test() || name.is_setup());
Expand Down
5 changes: 5 additions & 0 deletions crates/edr_solidity_tests/src/multi_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ impl MultiContractRunner {
})
}

/// Returns the known contracts.
pub fn known_contracts(&self) -> &ContractsByArtifact {
&self.known_contracts
}

/// Executes _all_ tests that match the given `filter`.
///
/// The same as [`test`](Self::test), but returns the results instead of
Expand Down
4 changes: 2 additions & 2 deletions crates/edr_solidity_tests/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,8 @@ impl<'a> ContractRunner<'a> {
find_time,
);

let identified_contracts =
has_invariants.then(|| load_contracts(setup.traces.clone(), &known_contracts));
let identified_contracts = has_invariants
.then(|| load_contracts(setup.traces.iter().map(|(_, t)| t), &known_contracts));
let test_results = functions
.par_iter()
.map(|&func| {
Expand Down
28 changes: 16 additions & 12 deletions crates/edr_solidity_tests/tests/it/helpers/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use edr_solidity_tests::{
};
use foundry_evm::{
decode::decode_console_logs,
traces::{render_trace_arena, CallTraceDecoderBuilder},
traces::{decode_trace_arena, render_trace_arena, CallTraceDecoderBuilder},
};
use futures::future::join_all;
use itertools::Itertools;
Expand Down Expand Up @@ -61,29 +61,33 @@ impl TestConfig {
/// * a test results deviates from the configured `should_fail` setting
pub async fn try_run(self) -> eyre::Result<()> {
let should_fail = self.should_fail;
let known_contracts = self.runner.known_contracts().clone();
let suite_result = self.test().await;
if suite_result.is_empty() {
eyre::bail!("empty test result");
}
for (_, SuiteResult { test_results, .. }) in suite_result {
for (test_name, result) in test_results {
for (test_name, mut result) in test_results {
if should_fail && (result.status == TestStatus::Success)
|| !should_fail && (result.status == TestStatus::Failure)
{
let logs = decode_console_logs(&result.logs);
let outcome = if should_fail { "fail" } else { "pass" };
let call_trace_decoder = CallTraceDecoderBuilder::default().build();
let decoded_traces = join_all(
result
.traces
.iter()
.map(|(_, a)| render_trace_arena(a, &call_trace_decoder))
.collect::<Vec<_>>(),
)
let call_trace_decoder = CallTraceDecoderBuilder::default()
.with_known_contracts(&known_contracts)
.build();
let decoded_traces = join_all(result.traces.iter_mut().map(|(_, arena)| {
let decoder = &call_trace_decoder;
async move {
decode_trace_arena(arena, decoder)
.await
.expect("Failed to decode traces");
render_trace_arena(arena)
}
}))
.await
.into_iter()
.map(|x| x.unwrap())
.collect::<Vec<_>>();
.collect::<Vec<String>>();
eyre::bail!(
"Test {} did not {} as expected.\nReason: {:?}\nLogs:\n{}\n\nTraces:\n{}",
test_name,
Expand Down
2 changes: 1 addition & 1 deletion crates/edr_solidity_tests/tests/it/repros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ test_repro!(6501, false, None, |res| {
assert_eq!(trace.depth, 1);
assert!(trace.success);
assert_eq!(
decoded.func,
decoded.call_data,
Some(DecodedCallData {
signature: expected.0.into(),
args: expected.1.into_iter().map(ToOwned::to_owned).collect(),
Expand Down
2 changes: 1 addition & 1 deletion crates/foundry/evm/evm/src/executors/invariant/replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub fn replay_run(

// Identify newly generated contracts, if they exist.
ided_contracts.extend(load_contracts(
vec![(TraceKind::Execution, call_result.traces.clone().unwrap())],
call_result.traces.as_slice(),
known_contracts,
));

Expand Down
2 changes: 0 additions & 2 deletions crates/foundry/evm/traces/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ alloy-sol-types.workspace = true
revm-inspectors.workspace = true

eyre = "0.6"
futures = "0.3"
hex.workspace = true
itertools.workspace = true
once_cell = "1"
Expand All @@ -32,7 +31,6 @@ serde.workspace = true
serde_json.workspace = true
tokio = { workspace = true, features = ["time", "macros"] }
tracing = "0.1"
yansi.workspace = true
rustc-hash.workspace = true

[dev-dependencies]
Expand Down
72 changes: 2 additions & 70 deletions crates/foundry/evm/traces/src/abi.rs
Original file line number Diff line number Diff line change
@@ -1,71 +1,9 @@
//! ABI related helper functions.
use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt};
use alloy_json_abi::{Event, Function};
use alloy_primitives::{hex, LogData};
use alloy_primitives::LogData;
use eyre::{Context, Result};

/// Given a function and a vector of string arguments, it proceeds to convert
/// the args to alloy [`DynSolValue`]s and then ABI encode them.
pub fn encode_function_args<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let params = std::iter::zip(&func.inputs, args)
.map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
.collect::<Result<Vec<_>>>()?;
func.abi_encode_input(params.as_slice()).map_err(Into::into)
}

/// Given a function and a vector of string arguments, it proceeds to convert
/// the args to alloy [`DynSolValue`]s and encode them using the packed
/// encoding.
pub fn encode_function_args_packed<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let params: Vec<Vec<u8>> = std::iter::zip(&func.inputs, args)
.map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
.collect::<Result<Vec<_>>>()?
.into_iter()
.map(|v| v.abi_encode_packed())
.collect();

Ok(params.concat())
}

/// Decodes the calldata of the function
pub fn abi_decode_calldata(
sig: &str,
calldata: &str,
input: bool,
fn_selector: bool,
) -> Result<Vec<DynSolValue>> {
let func = get_func(sig)?;
let calldata = hex::decode(calldata)?;

let mut calldata = calldata.as_slice();
// If function selector is prefixed in "calldata", remove it (first 4 bytes)
if input && fn_selector && calldata.len() >= 4 {
calldata = &calldata[4..];
}

let res = if input {
func.abi_decode_input(calldata, false)
} else {
func.abi_decode_output(calldata, false)
}?;

// in case the decoding worked but nothing was decoded
if res.is_empty() {
eyre::bail!("no data was decoded")
}

Ok(res)
}

/// Given a function signature string, it tries to parse it as a `Function`
pub fn get_func(sig: &str) -> Result<Function> {
Function::parse(sig).wrap_err("could not parse function signature")
Expand Down Expand Up @@ -103,15 +41,9 @@ pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event {
event
}

/// Helper function to coerce a value to a [`DynSolValue`] given a type string
pub fn coerce_value(ty: &str, arg: &str) -> Result<DynSolValue> {
let ty = DynSolType::parse(ty)?;
Ok(DynSolType::coerce_str(&ty, arg)?)
}

#[cfg(test)]
mod tests {
use alloy_dyn_abi::EventExt;
use alloy_dyn_abi::{DynSolValue, EventExt};
use alloy_primitives::{Address, B256, U256};

use super::*;
Expand Down
Loading

0 comments on commit 1bbc7c2

Please sign in to comment.