Skip to content

Commit 7e82dd9

Browse files
authored
fix(forge): properly decode raw Error(string) (#12536)
1 parent ac00e0b commit 7e82dd9

File tree

2 files changed

+46
-16
lines changed

2 files changed

+46
-16
lines changed

crates/cheatcodes/src/test/revert_handlers.rs

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::{Error, Result};
2+
use alloy_dyn_abi::{DynSolValue, ErrorExt};
23
use alloy_primitives::{Address, Bytes, address, hex};
34
use alloy_sol_types::{SolError, SolValue};
4-
use foundry_common::ContractsByArtifact;
5+
use foundry_common::{ContractsByArtifact, abi::get_error};
56
use foundry_evm_core::decode::RevertDecoder;
67
use revm::interpreter::{InstructionResult, return_ok};
78
use spec::Vm;
@@ -96,24 +97,34 @@ fn handle_revert(
9697
if actual_revert == expected_reason
9798
|| (is_cheatcode && memchr::memmem::find(&actual_revert, expected_reason).is_some())
9899
{
99-
Ok(())
100-
} else {
101-
let (actual, expected) = if let Some(contracts) = known_contracts {
102-
let decoder = RevertDecoder::new().with_abis(contracts.values().map(|c| &c.abi));
103-
(
104-
&decoder.decode(actual_revert.as_slice(), Some(status)),
105-
&decoder.decode(expected_reason, Some(status)),
106-
)
107-
} else {
108-
(&stringify(&actual_revert), &stringify(expected_reason))
109-
};
100+
return Ok(());
101+
}
110102

111-
if expected == actual {
112-
return Ok(());
113-
}
103+
// If expected reason is `Error(string)` then decode and compare with actual revert.
104+
// See <https://github.com/foundry-rs/foundry/issues/12511>
105+
if let Ok(e) = get_error("Error(string)")
106+
&& let Ok(dec) = e.decode_error(expected_reason)
107+
&& let Some(DynSolValue::String(revert_str)) = dec.body.first()
108+
&& revert_str.as_str() == String::from_utf8_lossy(&actual_revert)
109+
{
110+
return Ok(());
111+
}
114112

115-
Err(fmt_err!("Error != expected error: {} != {}", actual, expected))
113+
let (actual, expected) = if let Some(contracts) = known_contracts {
114+
let decoder = RevertDecoder::new().with_abis(contracts.values().map(|c| &c.abi));
115+
(
116+
&decoder.decode(actual_revert.as_slice(), Some(status)),
117+
&decoder.decode(expected_reason, Some(status)),
118+
)
119+
} else {
120+
(&stringify(&actual_revert), &stringify(expected_reason))
121+
};
122+
123+
if expected == actual {
124+
return Ok(());
116125
}
126+
127+
Err(fmt_err!("Error != expected error: {} != {}", actual, expected))
117128
}
118129

119130
pub(crate) fn handle_assume_no_revert(

testdata/default/cheats/ExpectRevert.t.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,22 @@ contract ExpectRevertCountWithReverter is Test {
412412
reverter.revertWithMessage("revert");
413413
}
414414
}
415+
416+
contract ExpectRevertWithErrorTest is Test {
417+
/// Ref: <https://github.com/foundry-rs/foundry/issues/12511>
418+
function test_f() external {
419+
bytes memory v = abi.encodeWithSignature("Error(string)", "");
420+
vm.expectRevert(v);
421+
this.f(v);
422+
423+
bytes memory v1 = abi.encodeWithSignature("Error(string)", unicode"🙀");
424+
vm.expectRevert(v1);
425+
this.f(v1);
426+
}
427+
428+
function f(bytes memory v) external pure {
429+
assembly {
430+
revert(add(v, 32), mload(v))
431+
}
432+
}
433+
}

0 commit comments

Comments
 (0)