From 9e41c08c2e9ca86070e81fca77e33389df8918aa Mon Sep 17 00:00:00 2001 From: cuiweixie Date: Tue, 13 Aug 2024 21:38:20 +0800 Subject: [PATCH 1/2] feat(cast run): try custom error decode from openchain signature database --- crates/cli/src/utils/cmd.rs | 3 +- crates/common/src/abi.rs | 6 +++- crates/evm/core/src/decode.rs | 43 ++++++++++++++++++++++++++-- crates/evm/traces/src/decoder/mod.rs | 10 ++++++- 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 1e4af95dfdf55..01e7ab3cd2e12 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -1,7 +1,7 @@ use alloy_json_abi::JsonAbi; use alloy_primitives::Address; use eyre::{Result, WrapErr}; -use foundry_common::{cli_warn, fs, TestFunctionExt}; +use foundry_common::{cli_warn, fs, selectors::OpenChainClient, TestFunctionExt}; use foundry_compilers::{ artifacts::{CompactBytecode, CompactDeployedBytecode, Settings}, cache::{CacheEntry, CompilerCache}, @@ -379,6 +379,7 @@ pub async fn handle_traces( Config::foundry_cache_dir(), config.offline, )?) + .with_openchain_client(OpenChainClient::new()?) .build(); let mut etherscan_identifier = EtherscanIdentifier::new(config, chain)?; diff --git a/crates/common/src/abi.rs b/crates/common/src/abi.rs index bcf82b1cddd4c..89cd0335751ee 100644 --- a/crates/common/src/abi.rs +++ b/crates/common/src/abi.rs @@ -1,7 +1,7 @@ //! ABI related helper functions. use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt}; -use alloy_json_abi::{Event, Function, Param}; +use alloy_json_abi::{Error, Event, Function, Param}; use alloy_primitives::{hex, Address, LogData}; use eyre::{Context, ContextCompat, Result}; use foundry_block_explorers::{contract::ContractMetadata, errors::EtherscanError, Client}; @@ -85,6 +85,10 @@ pub fn get_event(sig: &str) -> Result { Event::parse(sig).wrap_err("could not parse event signature") } +pub fn get_error(sig: &str) -> Result { + Error::parse(sig).wrap_err("could not parse error signature") +} + /// Given an event without indexed parameters and a rawlog, it tries to return the event with the /// proper indexed parameters. Otherwise, it returns the original event. pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event { diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 29f448bcebd58..fda1942aa4527 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -5,11 +5,15 @@ use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::{Error, JsonAbi}; use alloy_primitives::{hex, Log, Selector}; use alloy_sol_types::{SolCall, SolError, SolEventInterface, SolInterface, SolValue}; -use foundry_common::SELECTOR_LEN; +use foundry_common::{abi::get_error, selectors::OpenChainClient, SELECTOR_LEN}; use itertools::Itertools; use revm::interpreter::InstructionResult; use rustc_hash::FxHashMap; -use std::sync::OnceLock; +use std::{ + sync::{mpsc, OnceLock}, + thread, +}; +use tokio::runtime::Handle; /// Decode a set of logs, only returning logs from DSTest logging events and Hardhat's `console.log` pub fn decode_console_logs(logs: &[Log]) -> Vec { @@ -29,6 +33,7 @@ pub fn decode_console_log(log: &Log) -> Option { pub struct RevertDecoder { /// The custom errors to use for decoding. pub errors: FxHashMap>, + pub open_chain_client: Option, } impl Default for &RevertDecoder { @@ -176,6 +181,40 @@ impl RevertDecoder { return Some(std::str::from_utf8(err).unwrap().to_string()); } + // try from https://openchain.xyz + if let Some(client) = self.open_chain_client.clone() { + if let Ok(handle) = Handle::try_current() { + let (tx, rx) = mpsc::channel(); + let encoded_selector = hex::encode(selector); + thread::spawn(move || { + let result = + handle.block_on(client.decode_function_selector(&encoded_selector)); + tx.send(result).unwrap(); + }); + + let result = match rx.recv() { + Ok(Ok(sigs)) => Some(sigs), + Ok(Err(_)) | Err(_) => None, + }; + if let Some(sigs) = result { + for sig in sigs { + if let Ok(error) = get_error(&sig) { + if let Ok(decoded) = error.abi_decode_input(data, true) { + return Some(format!( + "{}({})", + error.name, + decoded + .iter() + .map(foundry_common::fmt::format_token) + .format(", ") + )); + } + } + } + } + } + } + // Generic custom error. Some(format!( "custom error {}:{}", diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index c8d1e461abbe8..9a961440a8470 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -9,7 +9,8 @@ use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt use alloy_json_abi::{Error, Event, Function, JsonAbi}; use alloy_primitives::{Address, LogData, Selector, B256}; use foundry_common::{ - abi::get_indexed_event, fmt::format_token, get_contract_name, ContractsByArtifact, SELECTOR_LEN, + abi::get_indexed_event, fmt::format_token, get_contract_name, selectors::OpenChainClient, + ContractsByArtifact, SELECTOR_LEN, }; use foundry_evm_core::{ abi::{Console, HardhatConsole, Vm, HARDHAT_CONSOLE_SELECTOR_PATCHES}, @@ -89,6 +90,13 @@ impl CallTraceDecoderBuilder { self } + /// Sets the openchain client. + #[inline] + pub fn with_openchain_client(mut self, client: OpenChainClient) -> Self { + self.decoder.revert_decoder.open_chain_client = Some(client); + self + } + /// Sets the debug identifier for the decoder. #[inline] pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self { From 8a81dd2da5a14bb38e3be4312c9d4c8f2ade4b10 Mon Sep 17 00:00:00 2001 From: cuiweixie Date: Thu, 15 Aug 2024 18:57:04 +0800 Subject: [PATCH 2/2] feat: move to revert_decoder level --- crates/evm/core/src/decode.rs | 18 +++++++++++------- crates/evm/traces/src/decoder/mod.rs | 10 +++++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index fda1942aa4527..699b7dc0c7c7f 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -181,6 +181,16 @@ impl RevertDecoder { return Some(std::str::from_utf8(err).unwrap().to_string()); } + // Generic custom error. + Some(format!( + "custom error {}:{}", + hex::encode(selector), + std::str::from_utf8(data).map_or_else(|_| trimmed_hex(data), String::from) + )) + } + + pub fn may_decode_using_open_chain(&self, err: &[u8]) -> Option { + let (selector, data) = err.split_at(SELECTOR_LEN); // try from https://openchain.xyz if let Some(client) = self.open_chain_client.clone() { if let Ok(handle) = Handle::try_current() { @@ -214,13 +224,7 @@ impl RevertDecoder { } } } - - // Generic custom error. - Some(format!( - "custom error {}:{}", - hex::encode(selector), - std::str::from_utf8(data).map_or_else(|_| trimmed_hex(data), String::from) - )) + None } } diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 9a961440a8470..07e561cd51abd 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -573,7 +573,15 @@ impl CallTraceDecoder { /// The default decoded return data for a trace. fn default_return_data(&self, trace: &CallTrace) -> Option { - (!trace.success).then(|| self.revert_decoder.decode(&trace.output, Some(trace.status))) + (!trace.success).then(|| { + let err_str = self.revert_decoder.decode(&trace.output, Some(trace.status)); + if err_str.contains("custom error") { + if let Some(err) = self.revert_decoder.may_decode_using_open_chain(&trace.output) { + return err; + } + } + err_str + }) } /// Decodes an event.