Skip to content

Commit

Permalink
Improve Nethermind revert error detection (#905)
Browse files Browse the repository at this point in the history
It looks like Nethermind has a couple of flavours of revert error messages for different `eth_call` class RPC methods. This PR adjusts the error detection to account for both flavours.

Specifically, we now handle:
```
web3 error: RPC error: Error {
    code: ServerError(-32015),
    message: "Reverted 0x...",
    data: None,
}
```

### Test Plan

Added unit test cases.
  • Loading branch information
nlordell authored Feb 16, 2023
1 parent 5717ea4 commit 6905ce4
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 46 deletions.
2 changes: 1 addition & 1 deletion ethcontract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ethcontract"
version = "0.23.1"
version = "0.23.2"
authors = ["Gnosis developers <[email protected]>"]
edition = "2021"
license = "MIT OR Apache-2.0"
Expand Down
109 changes: 64 additions & 45 deletions ethcontract/src/errors/nethermind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const MESSAGES: &[&str] = &["VM execution error", "VM execution error."];
/// Tries to get a more accurate error from a generic Nethermind JSON RPC error.
/// Returns `None` when a more accurate error cannot be determined.
pub fn get_encoded_error(err: &JsonrpcError) -> Option<ExecutionError> {
let message = get_error_message(err)?;
let message = get_error_message(err);
if let Some(hex) = message.strip_prefix(REVERTED) {
if hex.is_empty() {
return Some(ExecutionError::Revert(None));
Expand All @@ -39,8 +39,11 @@ pub fn get_encoded_error(err: &JsonrpcError) -> Option<ExecutionError> {
}

/// Returns the error message from the JSON RPC error data.
fn get_error_message(err: &JsonrpcError) -> Option<&'_ str> {
err.data.as_ref().and_then(|data| data.as_str())
fn get_error_message(err: &JsonrpcError) -> &str {
err.data
.as_ref()
.and_then(|data| data.as_str())
.unwrap_or(&err.message)
}

#[cfg(test)]
Expand All @@ -52,66 +55,82 @@ mod tests {
use crate::test::prelude::*;
use jsonrpc_core::ErrorCode;

pub fn rpc_error(data: &str) -> JsonrpcError {
JsonrpcError {
code: ErrorCode::from(-32015),
message: "VM execution error".to_owned(),
data: Some(json!(data)),
}
pub fn rpc_errors(data: &str) -> Vec<JsonrpcError> {
// Nethermind has two flavours of revert errors:
// ```
// {"jsonrpc":"2.0","error":{"code":-32015,"message":"VM execution error.","data":"Reverted 0x..."},"id":0}
// {"jsonrpc":"2.0","error":{"code":-32015,"message":"Reverted 0x..."},"id":1}
// ```
vec![
JsonrpcError {
code: ErrorCode::from(-32015),
message: "VM execution error".to_owned(),
data: Some(json!(data)),
},
JsonrpcError {
code: ErrorCode::from(-32015),
message: data.to_owned(),
data: None,
},
]
}

#[test]
fn execution_error_from_revert_with_message() {
let jsonrpc_err = rpc_error(&format!(
for jsonrpc_err in rpc_errors(&format!(
"Reverted {}",
revert::encode_reason_hex("message")
));
let err = get_encoded_error(&jsonrpc_err);

assert!(
matches!(
&err,
Some(ExecutionError::Revert(Some(reason))) if reason == "message"
),
"bad error conversion {:?}",
err
);
)) {
let err = get_encoded_error(&jsonrpc_err);

assert!(
matches!(
&err,
Some(ExecutionError::Revert(Some(reason))) if reason == "message"
),
"bad error conversion {:?}",
err
);
}
}

#[test]
fn execution_error_from_revert() {
let jsonrpc_err = rpc_error("Reverted 0x");
let err = get_encoded_error(&jsonrpc_err);

assert!(
matches!(err, Some(ExecutionError::Revert(None))),
"bad error conversion {:?}",
err
);
for jsonrpc_err in rpc_errors("Reverted 0x") {
let err = get_encoded_error(&jsonrpc_err);

assert!(
matches!(err, Some(ExecutionError::Revert(None))),
"bad error conversion {:?}",
err
);
}
}

#[test]
fn execution_error_from_revert_failed_decode() {
let jsonrpc_err = rpc_error("Reverted 0x01020304");
let err = get_encoded_error(&jsonrpc_err);

assert!(
matches!(err, Some(ExecutionError::Revert(None))),
"bad error conversion {:?}",
err
);
for jsonrpc_err in rpc_errors("Reverted 0x01020304") {
let err = get_encoded_error(&jsonrpc_err);

assert!(
matches!(err, Some(ExecutionError::Revert(None))),
"bad error conversion {:?}",
err
);
}
}

#[test]
fn execution_error_from_invalid_opcode() {
let jsonrpc_err = rpc_error("Bad instruction fd");
let err = get_encoded_error(&jsonrpc_err);

assert!(
matches!(err, Some(ExecutionError::InvalidOpcode)),
"bad error conversion {:?}",
err
);
for jsonrpc_err in rpc_errors("Bad instruction fd") {
let err = get_encoded_error(&jsonrpc_err);

assert!(
matches!(err, Some(ExecutionError::InvalidOpcode)),
"bad error conversion {:?}",
err
);
}
}

#[test]
Expand Down

0 comments on commit 6905ce4

Please sign in to comment.