Skip to content

Commit

Permalink
wip refactor traces
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes committed Oct 17, 2023
1 parent 60b3722 commit 547bf51
Show file tree
Hide file tree
Showing 19 changed files with 275 additions and 332 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

52 changes: 9 additions & 43 deletions crates/anvil/src/eth/backend/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ use ethers::{
DefaultFrame, Filter, FilteredParams, GethDebugTracingOptions, GethTrace, Log, OtherFields,
Trace, Transaction, TransactionReceipt, H160,
},
utils::{hex, keccak256, rlp},
utils::{keccak256, rlp},
};
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use foundry_evm::{
decode::{decode_custom_error_args, decode_revert},
decode::decode_revert,
executor::{
backend::{DatabaseError, DatabaseResult},
inspector::AccessListTracer,
Expand All @@ -66,7 +66,7 @@ use foundry_evm::{
revm::{
self,
db::CacheDB,
interpreter::{return_ok, InstructionResult},
interpreter::InstructionResult,
primitives::{
Account, BlockEnv, CreateScheme, EVMError, Env, ExecutionResult, InvalidHeader, Output,
SpecId, TransactTo, TxEnv, KECCAK_EMPTY,
Expand Down Expand Up @@ -285,7 +285,7 @@ impl Backend {
fork_genesis_infos.clear();

for res in genesis_accounts {
let (address, mut info) = res??;
let (address, mut info) = res.map_err(DatabaseError::display)??;
info.balance = self.genesis.balance;
db.insert_account(address, info.clone());

Expand Down Expand Up @@ -900,45 +900,11 @@ impl Backend {
node_info!(" Contract created: {:?}", contract);
}
node_info!(" Gas used: {}", receipt.gas_used());
match info.exit {
return_ok!() => (),
InstructionResult::OutOfFund => {
node_info!(" Error: reverted due to running out of funds");
}
InstructionResult::CallTooDeep => {
node_info!(" Error: reverted with call too deep");
}
InstructionResult::Revert => {
if let Some(ref r) = info.out {
if let Ok(reason) = decode_revert(r.as_ref(), None, None) {
node_info!(" Error: reverted with '{}'", reason);
} else {
match decode_custom_error_args(r, 5) {
// assuming max 5 args
Some(token) => {
node_info!(
" Error: reverted with custom error: {:?}",
token
);
}
None => {
node_info!(
" Error: reverted with custom error: {}",
hex::encode(r)
);
}
}
}
} else {
node_info!(" Error: reverted without a reason");
}
}
InstructionResult::OutOfGas => {
node_info!(" Error: ran out of gas");
}
reason => {
node_info!(" Error: failed due to {:?}", reason);
}
if !info.exit.is_ok() {
node_info!(
" Error: reverted with '{}'",
decode_revert(info.out.as_deref().unwrap_or_default(), None, None)
);
}
node_info!("");
}
Expand Down
1 change: 1 addition & 0 deletions crates/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ foundry-macros.workspace = true

# EVM
alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] }
alloy-sol-types.workspace = true
ethers = { workspace = true, features = ["ethers-solc"] }
hashbrown = { version = "0.14", features = ["serde"] }
revm = { workspace = true, default-features = false, features = [
Expand Down
10 changes: 5 additions & 5 deletions crates/evm/src/debug.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{abi::HEVM_ABI, CallKind};
use crate::CallKind;
use alloy_primitives::{Address, U256};
use revm::interpreter::{Memory, OpCode};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -178,11 +178,11 @@ impl Display for Instruction {
Instruction::Cheatcode(cheat) => write!(
f,
"VM_{}",
&*HEVM_ABI
.functions()
.find(|func| func.short_signature() == *cheat)
foundry_cheatcodes::Vm::CHEATCODES
.iter()
.find(|c| c.selector_bytes == *cheat)
.expect("unknown cheatcode found in debugger")
.name
.id
.to_uppercase()
),
}
Expand Down
208 changes: 71 additions & 137 deletions crates/evm/src/decode.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
//! Various utilities to decode test results
use crate::abi::ConsoleEvents::{self, *};
use alloy_primitives::B256;
use alloy_sol_types::{ContractError, SolCall, SolInterface};
use ethers::{
abi::{decode, AbiDecode, Contract as Abi, ParamType, RawLog, Token},
abi::{decode, Contract as Abi, ParamType, RawLog, Token},
contract::EthLogDecode,
prelude::U256,
types::Log,
};
use foundry_cheatcodes::impls::MAGIC_SKIP_BYTES;
use foundry_common::{abi::format_token, SELECTOR_LEN};
use foundry_utils::error::ERROR_PREFIX;
use foundry_cheatcodes::{impls::MAGIC_SKIP_BYTES, Vm};
use foundry_common::SELECTOR_LEN;
use itertools::Itertools;
use once_cell::sync::Lazy;
use revm::interpreter::{return_ok, InstructionResult};
Expand Down Expand Up @@ -71,149 +70,83 @@ pub fn decode_revert(
err: &[u8],
maybe_abi: Option<&Abi>,
status: Option<InstructionResult>,
) -> eyre::Result<String> {
) -> String {
if err.len() < SELECTOR_LEN {
if let Some(status) = status {
if !matches!(status, return_ok!()) {
return Ok(format!("EvmError: {status:?}"))
return format!("EvmError: {status:?}")
}
}
eyre::bail!("Not enough error data to decode")
}
match err[..SELECTOR_LEN] {
// keccak(Panic(uint256))
[78, 72, 123, 113] => {
// ref: https://soliditydeveloper.com/solidity-0.8
match err[err.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())
}
_ => {
eyre::bail!("Unsupported solidity builtin panic")
}
}
}
// keccak(Error(string))
[8, 195, 121, 160] => {
String::decode(&err[SELECTOR_LEN..]).map_err(|_| eyre::eyre!("Bad string decode"))
return if let Ok(s) = std::str::from_utf8(err) {
s.to_string()
} else {
hex::encode_prefixed(err)
}
// keccak(expectRevert(bytes))
[242, 141, 206, 179] => {
let err_data = &err[SELECTOR_LEN..];
if err_data.len() > 64 {
let len = U256::from(&err_data[32..64]).as_usize();
if err_data.len() > 64 + len {
let actual_err = &err_data[64..64 + len];
if let Ok(decoded) = decode_revert(actual_err, maybe_abi, None) {
// check if it's a builtin
return Ok(decoded)
} else if let Ok(as_str) = String::from_utf8(actual_err.to_vec()) {
// check if it's a true string
return Ok(as_str)
}
}
}
eyre::bail!("Non-native error and not string")
}

// `skip` special case
if err == MAGIC_SKIP_BYTES {
return "SKIPPED".to_string()
}

// `Panic(uint256)`, `Error(string)`, `CheatCodeError(string)`
if let Ok(e) = ContractError::<Vm::VmErrors>::abi_decode(err, true) {
return match e {
ContractError::CustomError(vm) => match vm {
Vm::VmErrors::CheatCodeError(cheatcode) => cheatcode.message,
},
ContractError::Panic(panic) => panic.to_string(),
ContractError::Revert(revert) => revert.to_string(),
}
// keccak(expectRevert(bytes4))
[195, 30, 176, 224] => {
let err_data = &err[SELECTOR_LEN..];
if err_data.len() == 32 {
let actual_err = &err_data[..SELECTOR_LEN];
if let Ok(decoded) = decode_revert(actual_err, maybe_abi, None) {
// it's a known selector
return Ok(decoded)
}

// `expectRevert(bytes)`
if let Ok(e) = Vm::expectRevert_2Call::abi_decode(err, true) {
return decode_revert(&e.revertData[..], maybe_abi, status)
}

// `expectRevert(bytes4)`
if let Ok(e) = Vm::expectRevert_1Call::abi_decode(err, true) {
return decode_revert(&e.revertData[..], maybe_abi, status)
}

// try to decode a custom error if provided an abi
if let Some(abi) = maybe_abi {
for abi_error in abi.errors() {
if abi_error.signature()[..SELECTOR_LEN] == err[..SELECTOR_LEN] {
// if we don't decode, don't return an error, try to decode as a string later
if let Ok(decoded) = abi_error.decode(&err[SELECTOR_LEN..]) {
let inputs = decoded
.iter()
.map(foundry_common::abi::format_token)
.collect::<Vec<_>>()
.join(", ");
return format!("{}({inputs})", abi_error.name)
}
}
eyre::bail!("Unknown error selector")
}
_ => {
// See if the revert is caused by a skip() call.
if err == MAGIC_SKIP_BYTES {
return Ok("SKIPPED".to_string())
}
// try to decode a custom error if provided an abi
if let Some(abi) = maybe_abi {
for abi_error in abi.errors() {
if abi_error.signature()[..SELECTOR_LEN] == err[..SELECTOR_LEN] {
// if we don't decode, don't return an error, try to decode as a
// string later
if let Ok(decoded) = abi_error.decode(&err[SELECTOR_LEN..]) {
let inputs = decoded
.iter()
.map(foundry_common::abi::format_token)
.collect::<Vec<_>>()
.join(", ");
return Ok(format!("{}({inputs})", abi_error.name))
}
}
}
}
// optimistically try to decode as string, unknown selector or `CheatcodeError`
String::decode(err)
.ok()
.or_else(|| {
// try decoding as cheatcode error
if err.starts_with(ERROR_PREFIX.as_slice()) {
String::decode(&err[ERROR_PREFIX.len()..]).ok()
} else {
None
}
})
.or_else(|| {
// try decoding as unknown err
String::decode(&err[SELECTOR_LEN..])
.map(|err_str| format!("{}:{err_str}", hex::encode(&err[..SELECTOR_LEN])))
.ok()
})
.or_else(|| {
// try to decode possible variations of custom error types
decode_custom_error(err).map(|token| {
let s = format!("Custom Error {}:", hex::encode(&err[..SELECTOR_LEN]));
}

let err_str = format_token(&token);
if err_str.starts_with('(') {
format!("{s}{err_str}")
} else {
format!("{s}({err_str})")
}
})
})
.ok_or_else(|| eyre::eyre!("Non-native error and not string"))
}
// `string`
if let Ok(s) = std::str::from_utf8(err) {
return s.to_string()
}

// Generic custom error
let (selector, err) = err.split_at(SELECTOR_LEN);
format!(
"Custom error {}:{}",
hex::encode(selector),
std::str::from_utf8(err).map_or_else(|_| trimmed_hex(err), String::from)
)
}

fn trimmed_hex(s: &[u8]) -> String {
let s = hex::encode(s);
if s.len() <= 32 {
s
} else {
format!("{}…{} ({} bytes)", &s[..16], &s[s.len() - 16..], s.len())
}
}

Expand Down Expand Up @@ -282,6 +215,7 @@ mod tests {
use ethers::{
abi::{AbiEncode, Address},
contract::EthError,
types::U256,
};

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/src/executor/fork/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ mod tests {
enable_caching: true,
url: ENDPOINT.to_string(),
env: env.clone(),
evm_opts,
evm_opts: todo!(),
};

let backend = Backend::spawn(Some(fork)).await;
Expand Down
Loading

0 comments on commit 547bf51

Please sign in to comment.