forest/rpc/methods/eth/
errors.rs1use crate::rpc::error::RpcErrorData;
5use crate::shim::clock::ChainEpoch;
6use crate::shim::error::ExitCode;
7use crate::utils::encoding::hex;
8use serde::Serialize;
9use std::fmt::Debug;
10use thiserror::Error;
11
12pub const EXECUTION_REVERTED_CODE: i32 = 3;
15pub const LIMIT_EXCEEDED_CODE: i32 = -32005;
18pub const NULL_ROUND_CODE: i32 = 12;
20
21#[derive(Clone, Debug, Error, Serialize)]
22pub enum EthErrors {
23 #[error("{message}")]
24 ExecutionReverted { message: String, data: String },
25 #[error("{message}")]
26 BlockRangeExceeded {
27 max: i64,
28 given: i64,
29 message: String,
30 },
31 #[error("events for the requested block are not yet available")]
32 EventsNotYetAvailable,
33 #[error("requested epoch was a null round ({epoch})")]
34 NullRound { epoch: ChainEpoch },
35}
36
37impl EthErrors {
38 pub fn execution_reverted(exit_code: ExitCode, reason: &str, error: &str, data: &[u8]) -> Self {
40 let revert_reason = if reason.is_empty() {
41 String::new()
42 } else {
43 format!(", revert reason=[{reason}]")
44 };
45
46 Self::ExecutionReverted {
47 message: format!(
48 "message execution failed (exit=[{exit_code}]{revert_reason}, vm error=[{error}])"
49 ),
50 data: hex::encode_prefixed(data),
51 }
52 }
53
54 pub fn limit_exceeded(max_block_range: i64, given: i64) -> Self {
55 Self::BlockRangeExceeded {
56 max: max_block_range,
57 given,
58 message: format!("block range exceeds maximum of {max_block_range} (got {given})"),
59 }
60 }
61
62 pub fn null_round(epoch: ChainEpoch) -> Self {
64 Self::NullRound { epoch }
65 }
66}
67
68impl RpcErrorData for EthErrors {
69 fn error_code(&self) -> Option<i32> {
70 match self {
71 EthErrors::ExecutionReverted { .. } => Some(EXECUTION_REVERTED_CODE),
72 EthErrors::BlockRangeExceeded { .. } => Some(LIMIT_EXCEEDED_CODE),
73 EthErrors::EventsNotYetAvailable => None,
74 EthErrors::NullRound { .. } => Some(NULL_ROUND_CODE),
75 }
76 }
77
78 fn error_message(&self) -> Option<String> {
79 match self {
80 EthErrors::ExecutionReverted { message, .. } => Some(message.clone()),
81 EthErrors::BlockRangeExceeded { message, .. } => Some(message.clone()),
82 EthErrors::EventsNotYetAvailable => Some(self.to_string()),
83 EthErrors::NullRound { .. } => Some(self.to_string()),
84 }
85 }
86
87 fn error_data(&self) -> Option<serde_json::Value> {
88 match self {
89 EthErrors::ExecutionReverted { data, .. } => {
90 Some(serde_json::Value::String(data.clone()))
91 }
92 EthErrors::BlockRangeExceeded { .. } => None,
93 EthErrors::EventsNotYetAvailable => None,
94 EthErrors::NullRound { epoch } => Some(serde_json::Value::from(*epoch)),
96 }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::rpc::error::ServerError;
104
105 #[test]
106 fn test_block_range_exceeded_converts_to_server_error_with_correct_code() {
107 let err = EthErrors::limit_exceeded(100, 500);
108 let server_err: ServerError = err.into();
109
110 assert_eq!(server_err.code(), LIMIT_EXCEEDED_CODE);
111 assert_eq!(
112 server_err.message(),
113 "block range exceeds maximum of 100 (got 500)"
114 );
115 }
116
117 #[test]
118 fn test_block_range_exceeded_via_anyhow_preserves_code() {
119 let eth_err = EthErrors::limit_exceeded(2880, 5000);
120 let anyhow_err: anyhow::Error = eth_err.into();
121 let server_err: ServerError = anyhow_err.into();
122
123 assert_eq!(server_err.code(), LIMIT_EXCEEDED_CODE);
124 assert_eq!(
125 server_err.message(),
126 "block range exceeds maximum of 2880 (got 5000)"
127 );
128 }
129
130 #[test]
131 fn test_null_round_converts_to_server_error_matching_lotus() {
132 let err = EthErrors::null_round(3847253);
133 let server_err: ServerError = err.into();
134
135 assert_eq!(server_err.code(), NULL_ROUND_CODE);
137 assert_eq!(
138 server_err.message(),
139 "requested epoch was a null round (3847253)"
140 );
141 assert_eq!(
142 server_err.data().map(|d| d.to_string()),
143 Some("3847253".to_string())
144 );
145 }
146
147 #[test]
148 fn test_null_round_via_anyhow_preserves_code() {
149 let anyhow_err: anyhow::Error = EthErrors::null_round(42).into();
150 let server_err: ServerError = anyhow_err.into();
151
152 assert_eq!(server_err.code(), NULL_ROUND_CODE);
153 assert_eq!(
154 server_err.message(),
155 "requested epoch was a null round (42)"
156 );
157 }
158
159 #[test]
160 fn test_events_not_yet_available_converts_to_server_error() {
161 let err = EthErrors::EventsNotYetAvailable;
162 let server_err: ServerError = err.into();
163
164 assert_eq!(
166 server_err.message(),
167 "events for the requested block are not yet available"
168 );
169 }
170}