Skip to main content

forest/rpc/methods/eth/
errors.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use crate::rpc::error::RpcErrorData;
5use crate::shim::error::ExitCode;
6use serde::Serialize;
7use std::fmt::Debug;
8use thiserror::Error;
9
10/// This error indicates that the execution reverted while executing the message.
11/// Error code 3 was introduced in geth v1.9.15 and is now expected by most Ethereum ecosystem tooling for automatic ABI decoding of revert reasons from the error data field.
12pub const EXECUTION_REVERTED_CODE: i32 = 3;
13/// This error indicates that the block range provided in the RPC exceeds the configured maximum
14/// It was introduced in EIP-1474
15pub const LIMIT_EXCEEDED_CODE: i32 = -32005;
16
17#[derive(Clone, Debug, Error, Serialize)]
18pub enum EthErrors {
19    #[error("{message}")]
20    ExecutionReverted { message: String, data: String },
21    #[error("{message}")]
22    BlockRangeExceeded {
23        max: i64,
24        given: i64,
25        message: String,
26    },
27}
28
29impl EthErrors {
30    /// Create a new ExecutionReverted error with formatted message
31    pub fn execution_reverted(exit_code: ExitCode, reason: &str, error: &str, data: &[u8]) -> Self {
32        let revert_reason = if reason.is_empty() {
33            String::new()
34        } else {
35            format!(", revert reason=[{reason}]")
36        };
37
38        Self::ExecutionReverted {
39            message: format!(
40                "message execution failed (exit=[{exit_code}]{revert_reason}, vm error=[{error}])"
41            ),
42            data: format!("0x{}", hex::encode(data)),
43        }
44    }
45
46    pub fn limit_exceeded(max_block_range: i64, given: i64) -> Self {
47        Self::BlockRangeExceeded {
48            max: max_block_range,
49            given,
50            message: format!("block range exceeds maximum of {max_block_range} (got {given})"),
51        }
52    }
53}
54
55impl RpcErrorData for EthErrors {
56    fn error_code(&self) -> Option<i32> {
57        match self {
58            EthErrors::ExecutionReverted { .. } => Some(EXECUTION_REVERTED_CODE),
59            EthErrors::BlockRangeExceeded { .. } => Some(LIMIT_EXCEEDED_CODE),
60        }
61    }
62
63    fn error_message(&self) -> Option<String> {
64        match self {
65            EthErrors::ExecutionReverted { message, .. } => Some(message.clone()),
66            EthErrors::BlockRangeExceeded { message, .. } => Some(message.clone()),
67        }
68    }
69
70    fn error_data(&self) -> Option<serde_json::Value> {
71        match self {
72            EthErrors::ExecutionReverted { data, .. } => {
73                Some(serde_json::Value::String(data.clone()))
74            }
75            EthErrors::BlockRangeExceeded { .. } => None,
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::rpc::error::ServerError;
84
85    #[test]
86    fn test_block_range_exceeded_converts_to_server_error_with_correct_code() {
87        let err = EthErrors::limit_exceeded(100, 500);
88        let server_err: ServerError = err.into();
89
90        assert_eq!(server_err.inner().code(), LIMIT_EXCEEDED_CODE);
91        assert_eq!(
92            server_err.message(),
93            "block range exceeds maximum of 100 (got 500)"
94        );
95    }
96
97    #[test]
98    fn test_block_range_exceeded_via_anyhow_preserves_code() {
99        let eth_err = EthErrors::limit_exceeded(2880, 5000);
100        let anyhow_err: anyhow::Error = eth_err.into();
101        let server_err: ServerError = anyhow_err.into();
102
103        assert_eq!(server_err.inner().code(), LIMIT_EXCEEDED_CODE);
104        assert_eq!(
105            server_err.message(),
106            "block range exceeds maximum of 2880 (got 5000)"
107        );
108    }
109}