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