use ethcontract_common::abi::{self, ParamType};
use ethcontract_common::hash::{self, H32};
use lazy_static::lazy_static;
lazy_static! {
static ref ERROR_SELECTOR: H32 = hash::function_selector("Error(string)");
}
pub fn decode_reason(bytes: &[u8]) -> Option<String> {
if (bytes.len() + 28) % 32 != 0 || bytes[0..4] != ERROR_SELECTOR[..] {
return None;
}
let reason = abi::decode(&[ParamType::String], &bytes[4..])
.ok()?
.pop()
.expect("decoded single parameter will yield single token")
.to_string();
Some(reason)
}
#[cfg(test)]
pub use tests::*;
#[cfg(test)]
mod tests {
use super::*;
use ethcontract_common::abi::{Function, Param, Token};
pub fn encode_reason(reason: &str) -> Vec<u8> {
#[allow(deprecated)]
let revert = Function {
name: "Error".into(),
inputs: vec![Param {
name: "".into(),
kind: ParamType::String,
internal_type: None,
}],
outputs: Vec::new(),
constant: None,
state_mutability: Default::default(),
};
revert
.encode_input(&[Token::String(reason.into())])
.expect("error encoding revert reason")
}
pub fn encode_reason_hex(reason: &str) -> String {
let encoded = encode_reason(reason);
format!("0x{}", hex::encode(encoded))
}
#[test]
fn decode_revert_reason() {
let reason = "ethcontract rocks!";
let encoded = encode_reason(reason);
assert_eq!(decode_reason(&encoded).as_deref(), Some(reason));
}
}