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/refactor: partial test runner refactor #7109

Merged
merged 8 commits into from
Feb 14, 2024
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
13 changes: 10 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ resolver = "2"
[workspace.package]
version = "0.2.0"
edition = "2021"
rust-version = "1.74" # Remember to update clippy.toml as well
rust-version = "1.75" # Remember to update clippy.toml as well
authors = ["Foundry Contributors"]
license = "MIT OR Apache-2.0"
homepage = "https://github.com/foundry-rs/foundry"
Expand All @@ -49,7 +49,10 @@ solang-parser.opt-level = 3
serde_json.opt-level = 3

# EVM
alloy-dyn-abi.opt-level = 3
alloy-json-abi.opt-level = 3
alloy-primitives.opt-level = 3
alloy-sol-type-parser.opt-level = 3
alloy-sol-types.opt-level = 3
hashbrown.opt-level = 3
keccak.opt-level = 3
Expand All @@ -62,12 +65,16 @@ sha2.opt-level = 3
sha3.opt-level = 3
tiny-keccak.opt-level = 3

# keystores
scrypt.opt-level = 3
# fuzzing
proptest.opt-level = 3
foundry-evm-fuzz.opt-level = 3

# forking
axum.opt-level = 3

# keystores
scrypt.opt-level = 3

# Local "release" mode, more optimized than dev but much faster to compile than release.
[profile.local]
inherits = "dev"
Expand Down
2 changes: 1 addition & 1 deletion clippy.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
msrv = "1.74"
msrv = "1.75"
56 changes: 24 additions & 32 deletions crates/common/src/calc.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
//! Commonly used calculations.

use alloy_primitives::{Sign, U256};
use std::ops::Div;

/// Returns the mean of the slice
/// Returns the mean of the slice.
#[inline]
pub fn mean(values: &[U256]) -> U256 {
pub fn mean(values: &[u64]) -> u64 {
if values.is_empty() {
return U256::ZERO
return 0;
}

values.iter().copied().fold(U256::ZERO, |sum, val| sum + val).div(U256::from(values.len()))
(values.iter().map(|x| *x as u128).sum::<u128>() / values.len() as u128) as u64
}

/// Returns the median of a _sorted_ slice
/// Returns the median of a _sorted_ slice.
#[inline]
pub fn median_sorted(values: &[U256]) -> U256 {
pub fn median_sorted(values: &[u64]) -> u64 {
if values.is_empty() {
return U256::ZERO
return 0;
}

let len = values.len();
let mid = len / 2;
if len % 2 == 0 {
(values[mid - 1] + values[mid]) / U256::from(2u64)
(values[mid - 1] + values[mid]) / 2
} else {
values[mid]
}
Expand Down Expand Up @@ -70,49 +69,42 @@ mod tests {

#[test]
fn calc_mean_empty() {
let values: [U256; 0] = [];
let m = mean(&values);
assert_eq!(m, U256::ZERO);
let m = mean(&[]);
assert_eq!(m, 0);
}

#[test]
fn calc_mean() {
let values = [
U256::ZERO,
U256::from(1),
U256::from(2u64),
U256::from(3u64),
U256::from(4u64),
U256::from(5u64),
U256::from(6u64),
];
let m = mean(&values);
assert_eq!(m, U256::from(3u64));
let m = mean(&[0, 1, 2, 3, 4, 5, 6]);
assert_eq!(m, 3);
}

#[test]
fn calc_mean_overflow() {
let m = mean(&[0, 1, 2, u32::MAX as u64, 3, u16::MAX as u64, u64::MAX, 6]);
assert_eq!(m, 2305843009750573057);
}

#[test]
fn calc_median_empty() {
let values: Vec<U256> = vec![];
let m = median_sorted(&values);
assert_eq!(m, U256::from(0));
let m = median_sorted(&[]);
assert_eq!(m, 0);
}

#[test]
fn calc_median() {
let mut values =
vec![29, 30, 31, 40, 59, 61, 71].into_iter().map(U256::from).collect::<Vec<_>>();
let mut values = vec![29, 30, 31, 40, 59, 61, 71];
values.sort();
let m = median_sorted(&values);
assert_eq!(m, U256::from(40));
assert_eq!(m, 40);
}

#[test]
fn calc_median_even() {
let mut values =
vec![80, 90, 30, 40, 50, 60, 10, 20].into_iter().map(U256::from).collect::<Vec<_>>();
let mut values = vec![80, 90, 30, 40, 50, 60, 10, 20];
values.sort();
let m = median_sorted(&values);
assert_eq!(m, U256::from(45));
assert_eq!(m, 45);
}

#[test]
Expand Down
28 changes: 27 additions & 1 deletion crates/common/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
///
/// Returns a value between `0.0` (identical) and `1.0` (completely different).
pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
// Make sure `a` is the longer one.
if a.len() < b.len() {
std::mem::swap(&mut a, &mut b);
}
Expand All @@ -112,11 +113,36 @@ pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
}

// Count different bytes.
n_different_bytes += std::iter::zip(a, b).filter(|(a, b)| a != b).count();
// SAFETY: `a` is longer than `b`.
n_different_bytes += unsafe { count_different_bytes(a, b) };

n_different_bytes as f64 / a.len() as f64
}

/// Returns the amount of different bytes between two slices.
///
/// # Safety
///
/// `a` must be at least as long as `b`.
unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
// This could've been written as `std::iter::zip(a, b).filter(|(x, y)| x != y).count()`,
// however this function is very hot, and has been written to be as primitive as
// possible for lower optimization levels.

let a_ptr = a.as_ptr();
let b_ptr = b.as_ptr();
let len = b.len();

let mut sum = 0;
let mut i = 0;
while i < len {
// SAFETY: `a` is at least as long as `b`, and `i` is in bound of `b`.
sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
i += 1;
}
sum
}

/// Flattens the contracts into (`id` -> (`JsonAbi`, `Vec<u8>`)) pairs
pub fn flatten_contracts(
contracts: &BTreeMap<ArtifactId, ContractBytecodeSome>,
Expand Down
3 changes: 3 additions & 0 deletions crates/evm/core/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ pub fn decode_revert(
})
}

/// Tries to decode an error message from the given revert bytes.
///
/// See [`decode_revert`] for more information.
pub fn maybe_decode_revert(
err: &[u8],
maybe_abi: Option<&JsonAbi>,
Expand Down
22 changes: 9 additions & 13 deletions crates/evm/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
extern crate tracing;

use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
use alloy_primitives::{Address, Bytes, Log, U256};
use alloy_primitives::{Address, Bytes, Log};
use foundry_common::{calc, contracts::ContractsByAddress};
use foundry_evm_coverage::HitMaps;
use foundry_evm_traces::CallTraceArena;
Expand Down Expand Up @@ -157,18 +157,16 @@ pub struct FuzzTestResult {
impl FuzzTestResult {
/// Returns the median gas of all test cases
pub fn median_gas(&self, with_stipend: bool) -> u64 {
let mut values =
self.gas_values(with_stipend).into_iter().map(U256::from).collect::<Vec<_>>();
let mut values = self.gas_values(with_stipend);
values.sort_unstable();
calc::median_sorted(&values).to::<u64>()
calc::median_sorted(&values)
}

/// Returns the average gas use of all test cases
pub fn mean_gas(&self, with_stipend: bool) -> u64 {
let mut values =
self.gas_values(with_stipend).into_iter().map(U256::from).collect::<Vec<_>>();
let mut values = self.gas_values(with_stipend);
values.sort_unstable();
calc::mean(&values).to::<u64>()
calc::mean(&values)
}

fn gas_values(&self, with_stipend: bool) -> Vec<u64> {
Expand Down Expand Up @@ -223,19 +221,17 @@ impl FuzzedCases {
/// Returns the median gas of all test cases
#[inline]
pub fn median_gas(&self, with_stipend: bool) -> u64 {
let mut values =
self.gas_values(with_stipend).into_iter().map(U256::from).collect::<Vec<_>>();
let mut values = self.gas_values(with_stipend);
values.sort_unstable();
calc::median_sorted(&values).to::<u64>()
calc::median_sorted(&values)
}

/// Returns the average gas use of all test cases
#[inline]
pub fn mean_gas(&self, with_stipend: bool) -> u64 {
let mut values =
self.gas_values(with_stipend).into_iter().map(U256::from).collect::<Vec<_>>();
let mut values = self.gas_values(with_stipend);
values.sort_unstable();
calc::mean(&values).to::<u64>()
calc::mean(&values)
}

#[inline]
Expand Down
20 changes: 16 additions & 4 deletions crates/evm/traces/src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,14 @@ pub struct CallTraceDecoder {
pub labels: HashMap<Address, String>,
/// Contract addresses that have a receive function.
pub receive_contracts: Vec<Address>,

/// All known functions.
pub functions: HashMap<Selector, Vec<Function>>,
/// All known events.
pub events: BTreeMap<(B256, usize), Vec<Event>>,
/// All known errors.
pub errors: JsonAbi,

/// A signature identifier for events and functions.
pub signature_identifier: Option<SingleSignaturesIdentifier>,
/// Verbosity level
Expand Down Expand Up @@ -143,7 +145,6 @@ impl CallTraceDecoder {

Self {
contracts: Default::default(),

labels: [
(CHEATCODE_ADDRESS, "VM".to_string()),
(HARDHAT_CONSOLE_ADDRESS, "console".to_string()),
Expand All @@ -152,6 +153,7 @@ impl CallTraceDecoder {
(TEST_CONTRACT_ADDRESS, "DefaultTestContract".to_string()),
]
.into(),
receive_contracts: Default::default(),

functions: hh_funcs()
.chain(
Expand All @@ -162,20 +164,30 @@ impl CallTraceDecoder {
)
.map(|(selector, func)| (selector, vec![func]))
.collect(),

events: Console::abi::events()
.into_values()
.flatten()
.map(|event| ((event.selector(), indexed_inputs(&event)), vec![event]))
.collect(),

errors: Default::default(),

signature_identifier: None,
receive_contracts: Default::default(),
verbosity: 0,
}
}

/// Clears all known addresses.
pub fn clear_addresses(&mut self) {
self.contracts.clear();

let default_labels = &Self::new().labels;
if self.labels.len() > default_labels.len() {
self.labels = default_labels.clone();
}

self.receive_contracts.clear();
}

/// Identify unknown addresses in the specified call trace using the specified identifier.
///
/// Unknown contracts are contracts that either lack a label or an ABI.
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/traces/src/identifier/etherscan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ impl TraceIdentifier for EtherscanIdentifier {
where
A: Iterator<Item = (&'a Address, Option<&'a [u8]>)>,
{
trace!(target: "etherscanidentifier", "identify {:?} addresses", addresses.size_hint().1);
trace!("identify {:?} addresses", addresses.size_hint().1);

let Some(client) = self.client.clone() else {
// no client was configured
Expand Down
2 changes: 1 addition & 1 deletion crates/forge/bin/cmd/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ impl CoverageArgs {
..Default::default()
})
.set_coverage(true)
.build(root.clone(), output, env, evm_opts)?;
.build(&root, output, env, evm_opts)?;

// Run tests
let known_contracts = runner.known_contracts.clone();
Expand Down
15 changes: 6 additions & 9 deletions crates/forge/bin/cmd/snapshot.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use super::{
test,
test::{Test, TestOutcome},
};
use super::test;
use alloy_primitives::U256;
use clap::{builder::RangedU64ValueParser, Parser, ValueHint};
use eyre::{Context, Result};
use forge::result::TestKindReport;
use forge::result::{SuiteTestResult, TestKindReport, TestOutcome};
use foundry_cli::utils::STATIC_FUZZ_SEED;
use once_cell::sync::Lazy;
use regex::Regex;
Expand Down Expand Up @@ -175,7 +172,7 @@ impl SnapshotConfig {
true
}

fn apply(&self, outcome: TestOutcome) -> Vec<Test> {
fn apply(&self, outcome: TestOutcome) -> Vec<SuiteTestResult> {
let mut tests = outcome
.into_tests()
.filter(|test| self.is_in_gas_range(test.gas_used()))
Expand Down Expand Up @@ -274,7 +271,7 @@ fn read_snapshot(path: impl AsRef<Path>) -> Result<Vec<SnapshotEntry>> {

/// Writes a series of tests to a snapshot file after sorting them
fn write_to_snapshot_file(
tests: &[Test],
tests: &[SuiteTestResult],
path: impl AsRef<Path>,
_format: Option<Format>,
) -> Result<()> {
Expand Down Expand Up @@ -318,7 +315,7 @@ impl SnapshotDiff {
/// Compares the set of tests with an existing snapshot
///
/// Returns true all tests match
fn check(tests: Vec<Test>, snaps: Vec<SnapshotEntry>, tolerance: Option<u32>) -> bool {
fn check(tests: Vec<SuiteTestResult>, snaps: Vec<SnapshotEntry>, tolerance: Option<u32>) -> bool {
let snaps = snaps
.into_iter()
.map(|s| ((s.contract_name, s.signature), s.gas_used))
Expand Down Expand Up @@ -352,7 +349,7 @@ fn check(tests: Vec<Test>, snaps: Vec<SnapshotEntry>, tolerance: Option<u32>) ->
}

/// Compare the set of tests with an existing snapshot
fn diff(tests: Vec<Test>, snaps: Vec<SnapshotEntry>) -> Result<()> {
fn diff(tests: Vec<SuiteTestResult>, snaps: Vec<SnapshotEntry>) -> Result<()> {
let snaps = snaps
.into_iter()
.map(|s| ((s.contract_name, s.signature), s.gas_used))
Expand Down
Loading
Loading