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: decode revert error #390

Merged
merged 4 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
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
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
Loading