Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cast decode-error #7775

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

1 change: 1 addition & 0 deletions crates/forge/Cargo.toml
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@ alloy-transport.workspace = true
alloy-signer.workspace = true
alloy-consensus.workspace = true
alloy-chains.workspace = true
alloy-sol-types.workspace = true

async-trait = "0.1"
clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] }
171 changes: 171 additions & 0 deletions crates/forge/bin/cmd/decode_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use clap::Parser;
use eyre::{eyre, Result};
use foundry_cli::opts::{CompilerArgs, CoreBuildArgs};
use foundry_common::compile::ProjectCompiler;
use foundry_compilers::artifacts::output_selection::ContractOutputSelection;
use std::fmt;

use alloy_dyn_abi::ErrorExt;
use alloy_json_abi::Error;
use alloy_sol_types::{Panic, Revert, SolError};

macro_rules! spaced_print {
($($arg:tt)*) => {
println!($($arg)*);
println!();
};
}

#[derive(Debug, Clone)]
enum RevertType {
Revert,
Panic,
/// The 4 byte signature of the error
Custom([u8; 4]),
}

impl fmt::Display for RevertType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RevertType::Revert => write!(f, "Revert"),
RevertType::Panic => write!(f, "Panic"),
RevertType::Custom(selector) => write!(f, "Custom(0x{})", hex::encode(selector)),
}
}
}

impl From<[u8; 4]> for RevertType {
fn from(selector: [u8; 4]) -> Self {
match selector {
Revert::SELECTOR => RevertType::Revert,
Panic::SELECTOR => RevertType::Panic,
_ => RevertType::Custom(selector),
}
}
}

/// CLI arguments for `forge inspect`.
#[derive(Clone, Debug, Parser)]
pub struct DecodeError {
/// The hex encoded revert data
revert_data: String,

/// All build arguments are supported
#[command(flatten)]
build: CoreBuildArgs,
}

impl DecodeError {
pub fn run(self) -> Result<()> {
let DecodeError { revert_data, build } = self;

if revert_data.len() < 8 {
return Err(eyre!("Revert data is too short"));
}

// convert to bytes and get the selector
let data_bytes = hex::decode(revert_data.trim_start_matches("0x"))?;
let selector: [u8; 4] = data_bytes[..4].try_into()?;

trace!(target: "forge", "running forge decode-error on error type {}", RevertType::from(selector));

// Make sure were gonna get the abi out
let mut cos = build.compiler.extra_output;
if !cos.iter().any(|selected| *selected == ContractOutputSelection::Abi) {
cos.push(ContractOutputSelection::Abi);
}

// Build modified Args
let modified_build_args = CoreBuildArgs {
compiler: CompilerArgs { extra_output: cos, ..build.compiler },
..build
};

// Build the project
if let Ok(project) = modified_build_args.project() {
let compiler = ProjectCompiler::new().quiet(true);
let output = compiler.compile(&project)?;

// search the project for the error
//
// we want to search even it matches the builtin errors because there could be a
// collision
let found_errs = output
.artifacts()
.filter_map(|(name, artifact)| {
Some((
name,
artifact.abi.as_ref()?.errors.iter().find_map(|(_, err)| {
// check if we have an error with a matching selector
// there can only be one per artifact
err.iter().find(|err| err.selector() == selector)
})?,
))
})
.collect::<Vec<_>>();

if !found_errs.is_empty() {
pretty_print_custom_errros(found_errs, &data_bytes);
}
} else {
tracing::trace!("No project found")
}

// try to decode the builtin errors if it matches
pretty_print_builtin_errors(selector.into(), &data_bytes);

Ok(())
}
}

fn pretty_print_custom_errros(found_errs: Vec<(String, &Error)>, data: &[u8]) {
let mut failures = Vec::with_capacity(found_errs.len());
let mut did_succeed = false;
for (artifact, dyn_err) in found_errs {
match dyn_err.decode_error(data) {
Ok(decoded) => {
did_succeed = true;

print_line();
println!("Artifact: {}", artifact);
println!("Error Name: {}", dyn_err.name);
for (param, value) in dyn_err.inputs.iter().zip(decoded.body.iter()) {
println!(" {}: {:?}", param.name, value);
}
println!(" ");
}
Err(e) => {
tracing::error!("Error decoding dyn err: {}", e);
failures.push(format!("decoding data for {} failed", dyn_err.signature()));
}
};
}

if !did_succeed {
for failure in failures {
tracing::error!("{}", failure);
}
}
}

fn pretty_print_builtin_errors(revert_type: RevertType, data: &[u8]) {
match revert_type {
RevertType::Revert => {
if let Ok(revert) = Revert::abi_decode(data, true) {
print_line();
spaced_print!("{:#?}\n", revert);
}
}
RevertType::Panic => {
if let Ok(panic) = Panic::abi_decode(data, true) {
print_line();
spaced_print!("{:#?}", panic);
}
}
_ => {}
}
}

fn print_line() {
spaced_print!("--------------------------------------------------------");
}
1 change: 1 addition & 0 deletions crates/forge/bin/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@ pub mod config;
pub mod coverage;
pub mod create;
pub mod debug;
pub mod decode_error;
pub mod doc;
pub mod flatten;
pub mod fmt;
1 change: 1 addition & 0 deletions crates/forge/bin/main.rs
Original file line number Diff line number Diff line change
@@ -110,6 +110,7 @@ fn main() -> Result<()> {
GenerateSubcommands::Test(cmd) => cmd.run(),
},
ForgeSubcommand::VerifyBytecode(cmd) => utils::block_on(cmd.run()),
ForgeSubcommand::DecodeError(cmd) => cmd.run(),
}
}

9 changes: 6 additions & 3 deletions crates/forge/bin/opts.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::cmd::{
bind::BindArgs, build::BuildArgs, cache::CacheArgs, clone::CloneArgs, config, coverage,
create::CreateArgs, debug::DebugArgs, doc::DocArgs, flatten, fmt::FmtArgs, geiger, generate,
init::InitArgs, inspect, install::InstallArgs, remappings::RemappingArgs, remove::RemoveArgs,
selectors::SelectorsSubcommands, snapshot, test, tree, update,
create::CreateArgs, debug::DebugArgs, decode_error, doc::DocArgs, flatten, fmt::FmtArgs,
geiger, generate, init::InitArgs, inspect, install::InstallArgs, remappings::RemappingArgs,
remove::RemoveArgs, selectors::SelectorsSubcommands, snapshot, test, tree, update,
};
use clap::{Parser, Subcommand, ValueHint};
use forge_script::ScriptArgs;
@@ -161,6 +161,9 @@ pub enum ForgeSubcommand {
/// Verify the deployed bytecode against its source.
#[clap(visible_alias = "vb")]
VerifyBytecode(VerifyBytecodeArgs),

/// Attempt to decode raw bytes from a revert message
DecodeError(decode_error::DecodeError),
}

#[cfg(test)]
Loading