Skip to content

Commit 8ac30d9

Browse files
authored
feat: dedup error messages (#9481)
1 parent 3a1e76b commit 8ac30d9

File tree

9 files changed

+74
-68
lines changed

9 files changed

+74
-68
lines changed

crates/cast/bin/cmd/send.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ pub enum SendTxSubcommands {
8585

8686
impl SendTxArgs {
8787
#[allow(unknown_lints, dependency_on_unit_never_type_fallback)]
88-
pub async fn run(self) -> Result<(), eyre::Report> {
88+
pub async fn run(self) -> eyre::Result<()> {
8989
let Self {
9090
eth,
9191
to,

crates/cheatcodes/src/error.rs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ impl Error {
206206
}
207207

208208
impl Drop for Error {
209-
#[inline]
210209
fn drop(&mut self) {
211210
if self.drop {
212211
drop(unsafe { Box::<[u8]>::from_raw(self.data.cast_mut()) });
@@ -224,21 +223,18 @@ impl From<Cow<'static, str>> for Error {
224223
}
225224

226225
impl From<String> for Error {
227-
#[inline]
228226
fn from(value: String) -> Self {
229227
Self::new_string(value)
230228
}
231229
}
232230

233231
impl From<&'static str> for Error {
234-
#[inline]
235232
fn from(value: &'static str) -> Self {
236233
Self::new_str(value)
237234
}
238235
}
239236

240237
impl From<Cow<'static, [u8]>> for Error {
241-
#[inline]
242238
fn from(value: Cow<'static, [u8]>) -> Self {
243239
match value {
244240
Cow::Borrowed(bytes) => Self::new_bytes(bytes),
@@ -248,21 +244,18 @@ impl From<Cow<'static, [u8]>> for Error {
248244
}
249245

250246
impl From<&'static [u8]> for Error {
251-
#[inline]
252247
fn from(value: &'static [u8]) -> Self {
253248
Self::new_bytes(value)
254249
}
255250
}
256251

257252
impl<const N: usize> From<&'static [u8; N]> for Error {
258-
#[inline]
259253
fn from(value: &'static [u8; N]) -> Self {
260254
Self::new_bytes(value)
261255
}
262256
}
263257

264258
impl From<Vec<u8>> for Error {
265-
#[inline]
266259
fn from(value: Vec<u8>) -> Self {
267260
Self::new_vec(value)
268261
}
@@ -279,7 +272,6 @@ impl From<Bytes> for Error {
279272
macro_rules! impl_from {
280273
($($t:ty),* $(,)?) => {$(
281274
impl From<$t> for Error {
282-
#[inline]
283275
fn from(value: $t) -> Self {
284276
Self::display(value)
285277
}
@@ -309,20 +301,14 @@ impl_from!(
309301
);
310302

311303
impl<T: Into<BackendError>> From<EVMError<T>> for Error {
312-
#[inline]
313304
fn from(err: EVMError<T>) -> Self {
314305
Self::display(BackendError::from(err))
315306
}
316307
}
317308

318309
impl From<eyre::Report> for Error {
319-
#[inline]
320310
fn from(err: eyre::Report) -> Self {
321-
let mut chained_cause = String::new();
322-
for cause in err.chain() {
323-
chained_cause.push_str(format!(" {cause};").as_str());
324-
}
325-
Self::display(chained_cause)
311+
Self::from(foundry_common::errors::display_chain(&err))
326312
}
327313
}
328314

crates/common/src/errors/mod.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,38 @@ pub use fs::FsPathError;
55

66
mod artifacts;
77
pub use artifacts::*;
8+
9+
/// Displays a chain of errors in a single line.
10+
pub fn display_chain(error: &eyre::Report) -> String {
11+
let mut causes = all_sources(error);
12+
// Deduplicate the common pattern `msg1: msg2; msg2` -> `msg1: msg2`.
13+
causes.dedup_by(|b, a| a.contains(b.as_str()));
14+
causes.join("; ")
15+
}
16+
17+
fn all_sources(err: &eyre::Report) -> Vec<String> {
18+
err.chain().map(|cause| cause.to_string().trim().to_string()).collect()
19+
}
20+
21+
#[cfg(test)]
22+
mod tests {
23+
use super::*;
24+
25+
#[test]
26+
fn dedups_contained() {
27+
#[derive(thiserror::Error, Debug)]
28+
#[error("my error: {0}")]
29+
struct A(#[from] B);
30+
31+
#[derive(thiserror::Error, Debug)]
32+
#[error("{0}")]
33+
struct B(String);
34+
35+
let ee = eyre::Report::from(A(B("hello".into())));
36+
assert_eq!(ee.chain().count(), 2, "{ee:?}");
37+
let full = all_sources(&ee).join("; ");
38+
assert_eq!(full, "my error: hello; hello");
39+
let chained = display_chain(&ee);
40+
assert_eq!(chained, "my error: hello");
41+
}
42+
}

crates/evm/core/src/backend/cow.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl<'a> CowBackend<'a> {
7373
self.spec_id = env.handler_cfg.spec_id;
7474
let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector);
7575

76-
let res = evm.transact().wrap_err("backend: failed while inspecting")?;
76+
let res = evm.transact().wrap_err("EVM error")?;
7777

7878
env.env = evm.context.evm.inner.env;
7979

crates/evm/core/src/backend/mod.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use crate::{
77
utils::{configure_tx_env, configure_tx_req_env, new_evm_with_inspector},
88
InspectorExt,
99
};
10-
use alloy_consensus::Transaction as TransactionTrait;
1110
use alloy_genesis::GenesisAccount;
1211
use alloy_network::{AnyRpcBlock, AnyTxEnvelope, TransactionResponse};
1312
use alloy_primitives::{keccak256, uint, Address, TxKind, B256, U256};
@@ -771,7 +770,7 @@ impl Backend {
771770
self.initialize(env);
772771
let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector);
773772

774-
let res = evm.transact().wrap_err("backend: failed while inspecting")?;
773+
let res = evm.transact().wrap_err("EVM error")?;
775774

776775
env.env = evm.context.evm.inner.env;
777776

@@ -1937,12 +1936,6 @@ fn commit_transaction(
19371936
persistent_accounts: &HashSet<Address>,
19381937
inspector: &mut dyn InspectorExt,
19391938
) -> eyre::Result<()> {
1940-
// TODO: Remove after https://github.com/foundry-rs/foundry/pull/9131
1941-
// if the tx has the blob_versioned_hashes field, we assume it's a Cancun block
1942-
if tx.blob_versioned_hashes().is_some() {
1943-
env.handler_cfg.spec_id = SpecId::CANCUN;
1944-
}
1945-
19461939
configure_tx_env(&mut env.env, tx);
19471940

19481941
let now = Instant::now();

crates/evm/core/src/opts.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS};
77
use foundry_config::{Chain, Config, GasLimit};
88
use revm::primitives::{BlockEnv, CfgEnv, TxEnv};
99
use serde::{Deserialize, Serialize};
10+
use std::fmt::Write;
1011
use url::Url;
1112

1213
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -129,13 +130,13 @@ impl EvmOpts {
129130
)
130131
.await
131132
.wrap_err_with(|| {
132-
let mut err_msg = "Could not instantiate forked environment".to_string();
133+
let mut msg = "Could not instantiate forked environment".to_string();
133134
if let Ok(url) = Url::parse(fork_url) {
134135
if let Some(provider) = url.host() {
135-
err_msg.push_str(&format!(" with provider {provider}"));
136+
write!(msg, " with provider {provider}").unwrap();
136137
}
137138
}
138-
err_msg
139+
msg
139140
})
140141
}
141142

crates/evm/evm/src/executors/mod.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -708,8 +708,12 @@ pub enum EvmError {
708708
#[error("{_0}")]
709709
Skip(SkipReason),
710710
/// Any other error.
711-
#[error(transparent)]
712-
Eyre(eyre::Error),
711+
#[error("{}", foundry_common::errors::display_chain(.0))]
712+
Eyre(
713+
#[from]
714+
#[source]
715+
eyre::Report,
716+
),
713717
}
714718

715719
impl From<ExecutionErr> for EvmError {
@@ -724,16 +728,6 @@ impl From<alloy_sol_types::Error> for EvmError {
724728
}
725729
}
726730

727-
impl From<eyre::Error> for EvmError {
728-
fn from(err: eyre::Report) -> Self {
729-
let mut chained_cause = String::new();
730-
for cause in err.chain() {
731-
chained_cause.push_str(format!("{cause}; ").as_str());
732-
}
733-
Self::Eyre(eyre::format_err!("{chained_cause}"))
734-
}
735-
}
736-
737731
/// The result of a deployment.
738732
#[derive(Debug)]
739733
pub struct DeployResult {

crates/forge/tests/cli/script.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2397,33 +2397,6 @@ Simulated On-chain Traces:
23972397
"#]]);
23982398
});
23992399

2400-
// Tests that chained errors are properly displayed.
2401-
// <https://github.com/foundry-rs/foundry/issues/9161>
2402-
forgetest_init!(
2403-
#[ignore]
2404-
should_display_evm_chained_error,
2405-
|prj, cmd| {
2406-
let script = prj
2407-
.add_source(
2408-
"Foo",
2409-
r#"
2410-
import "forge-std/Script.sol";
2411-
2412-
contract ContractScript is Script {
2413-
function run() public {
2414-
}
2415-
}
2416-
"#,
2417-
)
2418-
.unwrap();
2419-
cmd.arg("script").arg(script).args(["--fork-url", "https://public-node.testnet.rsk.co"]).assert_failure().stderr_eq(str![[r#"
2420-
Error: Failed to deploy script:
2421-
backend: failed while inspecting; header validation error: `prevrandao` not set; `prevrandao` not set;
2422-
2423-
"#]]);
2424-
}
2425-
);
2426-
24272400
forgetest_async!(should_detect_additional_contracts, |prj, cmd| {
24282401
let (_api, handle) = spawn(NodeConfig::test()).await;
24292402

crates/forge/tests/cli/test_cmd.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2659,7 +2659,7 @@ contract ForkTest is Test {
26592659
cmd.args(["test", "--mt", "test_fork_err_message"]).assert_failure().stdout_eq(str![[r#"
26602660
...
26612661
Ran 1 test for test/ForkTest.t.sol:ForkTest
2662-
[FAIL: vm.createSelectFork: Could not instantiate forked environment with provider eth-mainnet.g.alchemy.com;] test_fork_err_message() ([GAS])
2662+
[FAIL: vm.createSelectFork: Could not instantiate forked environment with provider eth-mainnet.g.alchemy.com] test_fork_err_message() ([GAS])
26632663
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]
26642664
...
26652665
@@ -2700,3 +2700,27 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)
27002700
27012701
"#]]);
27022702
});
2703+
2704+
// Tests that chained errors are properly displayed.
2705+
// <https://github.com/foundry-rs/foundry/issues/9161>
2706+
forgetest!(displays_chained_error, |prj, cmd| {
2707+
prj.add_test(
2708+
"Foo.t.sol",
2709+
r#"
2710+
contract ContractTest {
2711+
function test_anything(uint) public {}
2712+
}
2713+
"#,
2714+
)
2715+
.unwrap();
2716+
2717+
cmd.arg("test").arg("--gas-limit=100").assert_failure().stdout_eq(str![[r#"
2718+
...
2719+
Failing tests:
2720+
Encountered 1 failing test in test/Foo.t.sol:ContractTest
2721+
[FAIL: EVM error; transaction validation error: call gas cost exceeds the gas limit] setUp() ([GAS])
2722+
2723+
Encountered a total of 1 failing tests, 0 tests succeeded
2724+
2725+
"#]]);
2726+
});

0 commit comments

Comments
 (0)