Skip to content

Commit

Permalink
fix(forge): fix errors on known function reverts (foundry-rs#298)
Browse files Browse the repository at this point in the history
* fix errors on known functions

* better match statement

* decode *all* solidity errors

* fmt

* nits

* better decode_revert

* cleanup

* fmt

* fixes

* remove debug print
  • Loading branch information
brockelmore authored Dec 24, 2021
1 parent d5b7f11 commit 8c234f7
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 14 deletions.
22 changes: 21 additions & 1 deletion evm-adapters/src/call_tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,11 +466,21 @@ impl CallTrace {
strings,
);

if !self.output.is_empty() {
if !self.output.is_empty() && self.success {
return Output::Token(
func.decode_output(&self.output[..])
.expect("Bad func output decode"),
)
} else if !self.output.is_empty() && !self.success {
if let Ok(decoded_error) =
foundry_utils::decode_revert(&self.output[..])
{
return Output::Token(vec![ethers::abi::Token::String(
decoded_error,
)])
} else {
return Output::Raw(self.output.clone())
}
} else {
return Output::Raw(vec![])
}
Expand All @@ -491,6 +501,11 @@ impl CallTrace {
}
);

if !self.success {
if let Ok(decoded_error) = foundry_utils::decode_revert(&self.output[..]) {
return Output::Token(vec![ethers::abi::Token::String(decoded_error)])
}
}
return Output::Raw(self.output[..].to_vec())
}
}
Expand Down Expand Up @@ -518,6 +533,11 @@ impl CallTrace {
},
);

if !self.success {
if let Ok(decoded_error) = foundry_utils::decode_revert(&self.output[..]) {
return Output::Token(vec![ethers::abi::Token::String(decoded_error)])
}
}
Output::Raw(self.output[..].to_vec())
}
}
Expand Down
5 changes: 4 additions & 1 deletion evm-adapters/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ impl<'a, S, E: Evm<S>> FuzzedExecutor<'a, E, S> {
"{}, expected failure: {}, reason: '{}'",
func.name,
should_fail,
foundry_utils::decode_revert(returndata.as_ref())?
match foundry_utils::decode_revert(returndata.as_ref()) {
Ok(e) => e,
Err(e) => e.to_string(),
}
);

// push test case to the case set
Expand Down
7 changes: 0 additions & 7 deletions forge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ pub use runner::{ContractRunner, TestKind, TestKindGas, TestResult};
mod multi_runner;
pub use multi_runner::{MultiContractRunner, MultiContractRunnerBuilder};

use ethers::abi;
use eyre::Result;

pub fn decode_revert(error: &[u8]) -> Result<String> {
Ok(abi::decode(&[abi::ParamType::String], &error[4..])?[0].to_string())
}

#[cfg(test)]
pub mod test_helpers {
use ethers::{
Expand Down
71 changes: 66 additions & 5 deletions utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,73 @@ pub fn remove_extra_costs(gas: U256, calldata: &[u8]) -> U256 {

/// Given an ABI encoded error string with the function signature `Error(string)`, it decodes
/// it and returns the revert error message.
pub fn decode_revert(error: &[u8]) -> std::result::Result<String, ethers_core::abi::Error> {
let error = error.strip_prefix(&ethers_core::utils::id("Error(string)")).unwrap_or(error);
if !error.is_empty() {
Ok(abi::decode(&[abi::ParamType::String], error)?[0].to_string())
pub fn decode_revert(error: &[u8]) -> Result<String> {
if error.len() >= 4 {
match error[0..4] {
// keccak(Panic(uint256))
[78, 72, 123, 113] => {
// ref: https://soliditydeveloper.com/solidity-0.8
match error[error.len() - 1] {
1 => {
// assert
Ok("Assertion violated".to_string())
}
17 => {
// safemath over/underflow
Ok("Arithmetic over/underflow".to_string())
}
18 => {
// divide by 0
Ok("Division or modulo by 0".to_string())
}
33 => {
// conversion into non-existent enum type
Ok("Conversion into non-existent enum type".to_string())
}
34 => {
// incorrectly encoded storage byte array
Ok("Incorrectly encoded storage byte array".to_string())
}
49 => {
// pop() on empty array
Ok("`pop()` on empty array".to_string())
}
50 => {
// index out of bounds
Ok("Index out of bounds".to_string())
}
65 => {
// allocating too much memory or creating too large array
Ok("Memory allocation overflow".to_string())
}
81 => {
// calling a zero initialized variable of internal function type
Ok("Calling a zero initialized variable of internal function type"
.to_string())
}
_ => Err(eyre::Error::msg("Unsupported solidity builtin panic")),
}
}
// keccak(Error(string))
[8, 195, 121, 160] => {
if let Ok(decoded) = abi::decode(&[abi::ParamType::String], &error[4..]) {
Ok(decoded[0].to_string())
} else {
Err(eyre::Error::msg("Bad string decode"))
}
}
_ => {
// evm_error will sometimes not include the function selector for the error,
// optimistically try to decode
if let Ok(decoded) = abi::decode(&[abi::ParamType::String], error) {
Ok(decoded[0].to_string())
} else {
Err(eyre::Error::msg("Non-native error and not string"))
}
}
}
} else {
Ok("No revert reason found".to_owned())
Err(eyre::Error::msg("Not enough error data to decode"))
}
}

Expand Down

0 comments on commit 8c234f7

Please sign in to comment.