Skip to content

Commit

Permalink
feat: decode revert error (#390)
Browse files Browse the repository at this point in the history
  • Loading branch information
blckngm committed Dec 7, 2023
2 parents 9aaa906 + 5d01c7b commit fbb4dce
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 5 deletions.
13 changes: 10 additions & 3 deletions crates/relayer/src/chain/axon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ use super::{
use tokio::runtime::Runtime as TokioRuntime;

pub mod contract;
mod eth_err;
mod monitor;
mod msg;
mod rpc;
Expand Down Expand Up @@ -1370,9 +1371,15 @@ impl AxonChain {
macro_rules! convert {
($self:ident, $msg:ident, $eventy:ty, $method:ident) => {{
let msg: $eventy = $msg.try_into()?;
$self
.rt
.block_on(async { Ok($self.contract()?.$method(msg.clone()).send().await?.await?) })
$self.rt.block_on(async {
Ok($self
.contract()?
.$method(msg.clone())
.send()
.await
.map_err(decode_revert_error)?
.await?)
})
}};
}

Expand Down
104 changes: 104 additions & 0 deletions crates/relayer/src/chain/axon/eth_err.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use ethers::abi::Uint;
use ethers::contract::EthCall;

/// For decoding and displaying `Panic(uint256)` errors.
///
/// EthError derived decode_with_selector is buggy, so we derive `EthCall` instead. Decode with `AbiDecode::decode`.
#[derive(EthCall)]
pub struct Panic(Uint);

impl std::fmt::Display for Panic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let e = PanicError::from_code(self.0.low_u32());
write!(f, "Panic code: {:#x}, {e}", self.0)
}
}

enum PanicError {
Generic,
AssertFailed,
ArithmeticOverflow = 0x11,
DivisionByZero,
InvalidEnumConversion = 0x21,
InvalidEncoding,
EmptyArrayPop = 0x31,
OutOfBoundsAccess,
ExcessiveAllocation = 0x41,
UninitializedInternalFunction = 0x51,
Unknown,
}

impl PanicError {
fn from_code(code: u32) -> Self {
match code {
0 => PanicError::Generic,
0x01 => PanicError::AssertFailed,
0x11 => PanicError::ArithmeticOverflow,
0x12 => PanicError::DivisionByZero,
0x21 => PanicError::InvalidEnumConversion,
0x22 => PanicError::InvalidEncoding,
0x31 => PanicError::EmptyArrayPop,
0x32 => PanicError::OutOfBoundsAccess,
0x41 => PanicError::ExcessiveAllocation,
0x51 => PanicError::UninitializedInternalFunction,
_ => PanicError::Unknown,
}
}
}

impl std::fmt::Display for PanicError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let desc = match self {
PanicError::Generic => "Generic compiler inserted panic",
PanicError::AssertFailed => "Assertion failed",
PanicError::ArithmeticOverflow => "Arithmetic operation resulted in overflow",
PanicError::DivisionByZero => "Division or modulo by zero",
PanicError::InvalidEnumConversion => "Invalid enum conversion",
PanicError::InvalidEncoding => "Invalid encoding",
PanicError::EmptyArrayPop => "Attempted to pop an empty array",
PanicError::OutOfBoundsAccess => "Out-of-bounds access",
PanicError::ExcessiveAllocation => "Excessive memory allocation",
PanicError::UninitializedInternalFunction => {
"Called an uninitialized internal function"
}
PanicError::Unknown => "Unknown panic",
};
write!(f, "{desc}")
}
}

#[cfg(test)]
mod test {
use ethers::{abi::AbiDecode, contract::EthError};

use super::Panic;

fn parse_abi_err_data(err: &str) -> String {
let revert_data = hex::decode(
err.strip_prefix("Contract call reverted with data: 0x")
.unwrap(),
)
.unwrap();
if let Ok(p) = Panic::decode(&revert_data) {
p.to_string()
} else if let Some(s) = String::decode_with_selector(&revert_data) {
s
} else {
panic!("failed to decode")
}
}

#[test]
fn test_sol_revert() {
let err_string = "Contract call reverted with data: 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001c74657374206661696c656420746f2063726561746520636c69656e7400000000";
let err = parse_abi_err_data(err_string);
assert_eq!(err, "test failed to create client");
}

#[test]
fn test_sol_panic() {
let err_string = "Contract call reverted with data: 0x4e487b710000000000000000000000000000000000000000000000000000000000000012";
let err = parse_abi_err_data(err_string);
assert_eq!(err, "Panic code: 0x12, Division or modulo by zero");
}
}
17 changes: 15 additions & 2 deletions crates/relayer/src/chain/axon/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use std::str::FromStr;
use axon_tools::types::{Block as AxonBlock, Proof as AxonProof, ValidatorExtend};

use crate::{
chain::SEC_TO_NANO,
chain::{axon::eth_err::Panic, SEC_TO_NANO},
client_state::{AnyClientState, IdentifiedAnyClientState},
consensus_state::AnyConsensusState,
error::Error,
event::IbcEventWithHeight,
ibc_contract::OwnableIBCHandlerEvents,
};
use ethers::types::H256;
use ethers::{abi::AbiDecode, contract::ContractError, providers::Middleware, types::H256};
use ibc_relayer_types::{
clients::{
ics07_axon::{client_state::AxonClientState, consensus_state::AxonConsensusState},
Expand All @@ -29,6 +29,19 @@ pub fn convert_err<T: ToString>(err: T) -> Error {
Error::other_error(err.to_string())
}

pub fn decode_revert_error<M>(err: ContractError<M>) -> eyre::Report
where
M: Middleware + 'static,
{
if let Some(r) = err.decode_revert::<String>() {
eyre::eyre!("Contract call reverted: {r}")
} else if let Some(p) = err.as_revert().and_then(|d| Panic::decode(d).ok()) {
eyre::eyre!("Contract call reverted: {p}")
} else {
err.into()
}
}

pub fn to_identified_any_client_state(
client_state: &ethers::core::types::Bytes,
) -> Result<IdentifiedAnyClientState, Error> {
Expand Down

0 comments on commit fbb4dce

Please sign in to comment.