1use crate::ui::spinner::Spinner;
22use alloy_json_abi::JsonAbi;
23use alloy_primitives::{Address, Bytes, U256};
24use edb_common::types::{CallableAbiInfo, Code, EdbSolValue, SnapshotInfo, Trace};
25use eyre::Result;
26use jsonrpsee::{
27 core::client::ClientT,
28 http_client::{HttpClient, HttpClientBuilder},
29};
30use serde_json::Value;
31use std::{
32 collections::HashMap,
33 sync::{Arc, RwLock},
34 time::Duration,
35};
36use tracing::{debug, error};
37
38macro_rules! rpc_params {
48 () => {
49 Vec::<serde_json::Value>::new()
50 };
51 ($($param:expr),+ $(,)?) => {
52 vec![
53 $(serde_json::to_value($param).unwrap_or(serde_json::Value::Null)),+
54 ]
55 };
56}
57#[derive(Debug)]
59pub struct RpcClient {
60 client: HttpClient,
61 server_url: String,
62 spinner: Arc<RwLock<Spinner>>,
64}
65
66impl RpcClient {
67 pub async fn new(server_url: &str) -> Result<Self> {
69 let client = HttpClientBuilder::default()
70 .request_timeout(Duration::from_secs(30))
71 .build(server_url)?;
72
73 debug!("Created RPC client for: {}", server_url);
74 Ok(Self {
75 client,
76 server_url: server_url.to_string(),
77 spinner: Arc::new(RwLock::new(Spinner::new(None, None))),
78 })
79 }
80
81 pub async fn test_connection(server_url: &str) -> Result<()> {
83 debug!("Testing connection to: {}", server_url);
84
85 let client = HttpClientBuilder::default()
86 .request_timeout(Duration::from_secs(5))
87 .build(server_url)?;
88
89 match client.request::<Value, _>("debug.getSnapshotCount", rpc_params!()).await {
91 Ok(_) => {
92 debug!("Connection test successful for: {}", server_url);
93 Ok(())
94 }
95 Err(e) => {
96 debug!("Connection test failed for {}: {}", server_url, e);
97 Err(e.into())
98 }
99 }
100 }
101
102 async fn request_with_spinner(
104 &self,
105 method: &str,
106 params: Vec<Value>,
107 operation_name: &str,
108 ) -> Result<Value> {
109 self.start_loading(operation_name);
110 debug!("Making RPC request: {}", operation_name);
111
112 let result = match self.client.request(method, params).await {
113 Ok(result) => {
114 debug!("{} successful: {:?}", operation_name, result);
115 Ok(result)
116 }
117 Err(e) => {
118 error!("{} failed: {}", operation_name, e);
119 Err(e.into())
120 }
121 };
122
123 self.finish_loading();
124 result
125 }
126
127 pub fn server_url(&self) -> &str {
129 &self.server_url
130 }
131
132 pub fn spinner(&self) -> Arc<RwLock<Spinner>> {
134 Arc::clone(&self.spinner)
135 }
136
137 pub fn is_loading(&self) -> bool {
139 self.spinner.read().unwrap().is_loading()
140 }
141
142 pub fn spinner_display(&self) -> String {
144 self.spinner.read().unwrap().display_text()
145 }
146
147 fn start_loading(&self, operation: &str) {
149 self.spinner.write().unwrap().start_loading(operation);
150 debug!("Started loading spinner: {}", operation);
151 }
152
153 fn finish_loading(&self) {
155 self.spinner.write().unwrap().finish_loading();
156 debug!("Finished loading spinner");
157 }
158
159 pub fn tick(&self) {
161 self.spinner.write().unwrap().tick();
162 }
163
164 pub async fn health_check(&self) -> Result<Value> {
166 debug!("Checking server health");
167
168 let health_url = format!("{}/health", self.server_url.trim_end_matches('/'));
170
171 let response = reqwest::get(&health_url).await?;
172 let health_data: Value = response.json().await?;
173
174 debug!("Server health: {:?}", health_data);
175 Ok(health_data)
176 }
177}
178
179impl RpcClient {
181 pub async fn get_trace(&self) -> Result<Trace> {
183 let value = self
184 .request_with_spinner("edb_getTrace", rpc_params!(), "Fetching execution trace")
185 .await?;
186 serde_json::from_value(value).map_err(|e| eyre::eyre!("Failed to parse trace: {}", e))
187 }
188
189 pub async fn get_contract_abi(
191 &self,
192 address: Address,
193 recompiled: bool,
194 ) -> Result<Option<JsonAbi>> {
195 let value = self
196 .request_with_spinner(
197 "edb_getContractABI",
198 rpc_params!(address, recompiled),
199 &format!("Fetching contract ABI for {address}"),
200 )
201 .await?;
202
203 serde_json::from_value(value)
204 .map_err(|e| eyre::eyre!("Failed to parse contract ABI: {}", e))
205 }
206
207 pub async fn get_callable_abi(&self, address: Address) -> Result<Vec<CallableAbiInfo>> {
209 let value = self
210 .request_with_spinner(
211 "edb_getCallableABI",
212 rpc_params!(address),
213 &format!("Fetching callable ABI for {address}"),
214 )
215 .await?;
216
217 serde_json::from_value(value)
218 .map_err(|e| eyre::eyre!("Failed to parse callable ABI: {}", e))
219 }
220
221 pub async fn get_constructor_args(&self, address: Address) -> Result<Option<Bytes>> {
223 let value = self
224 .request_with_spinner(
225 "edb_getConstructorArgs",
226 rpc_params!(address),
227 &format!("Fetching contract constructor arguments for {address}"),
228 )
229 .await?;
230
231 serde_json::from_value(value)
232 .map_err(|e| eyre::eyre!("Failed to parse contract constructor arguments: {}", e))
233 }
234
235 pub async fn get_snapshot_count(&self) -> Result<usize> {
237 let value = self
238 .request_with_spinner(
239 "edb_getSnapshotCount",
240 rpc_params!(),
241 "Getting total snapshot count",
242 )
243 .await?;
244
245 serde_json::from_value(value)
246 .map_err(|e| eyre::eyre!("Failed to parse snapshot count: {}", e))
247 }
248
249 pub async fn get_snapshot_info(&self, snapshot_id: usize) -> Result<SnapshotInfo> {
251 let value = self
252 .request_with_spinner(
253 "edb_getSnapshotInfo",
254 rpc_params!(snapshot_id),
255 &format!("Getting info for snapshot {snapshot_id}"),
256 )
257 .await?;
258
259 serde_json::from_value(value)
260 .map_err(|e| eyre::eyre!("Failed to parse snapshot info: {}", e))
261 }
262
263 pub async fn get_code(&self, snapshot_id: usize) -> Result<Code> {
265 let value = self
266 .request_with_spinner(
267 "edb_getCode",
268 rpc_params!(snapshot_id),
269 &format!("Getting code for snapshot {snapshot_id}"),
270 )
271 .await?;
272
273 serde_json::from_value(value).map_err(|e| eyre::eyre!("Failed to parse code: {}", e))
274 }
275
276 pub async fn get_next_call(&self, snapshot_id: usize) -> Result<usize> {
278 let value = self
279 .request_with_spinner(
280 "edb_getNextCall",
281 rpc_params!(snapshot_id),
282 &format!("Getting next call for snapshot {snapshot_id}"),
283 )
284 .await?;
285
286 serde_json::from_value(value).map_err(|e| eyre::eyre!("Failed to parse next call: {}", e))
287 }
288
289 pub async fn get_prev_call(&self, snapshot_id: usize) -> Result<usize> {
291 let value = self
292 .request_with_spinner(
293 "edb_getPrevCall",
294 rpc_params!(snapshot_id),
295 &format!("Getting prev call for snapshot {snapshot_id}"),
296 )
297 .await?;
298
299 serde_json::from_value(value).map_err(|e| eyre::eyre!("Failed to parse prev call: {}", e))
300 }
301
302 pub async fn get_storage(&self, snapshot_id: usize, slot: U256) -> Result<U256> {
304 let value = self
305 .request_with_spinner(
306 "edb_getStorage",
307 rpc_params!(snapshot_id, slot),
308 &format!("Getting storage for snapshot {snapshot_id} at slot {slot}"),
309 )
310 .await?;
311
312 serde_json::from_value(value)
313 .map_err(|e| eyre::eyre!("Failed to parse storage value: {}", e))
314 }
315
316 pub async fn get_storage_diff(
318 &self,
319 snapshot_id: usize,
320 ) -> Result<HashMap<U256, (U256, U256)>> {
321 let value = self
322 .request_with_spinner(
323 "edb_getStorageDiff",
324 rpc_params!(snapshot_id),
325 &format!("Getting storage diff for snapshot {snapshot_id}"),
326 )
327 .await?;
328
329 serde_json::from_value(value)
330 .map_err(|e| eyre::eyre!("Failed to parse storage diff: {}", e))
331 }
332
333 pub async fn eval_on_snapshot(
335 &self,
336 snapshot_id: usize,
337 expr: &str,
338 ) -> Result<core::result::Result<EdbSolValue, String>> {
339 let value = self
340 .request_with_spinner(
341 "edb_evalOnSnapshot",
342 rpc_params!(snapshot_id, expr),
343 &format!("Evaluating expression on snapshot {snapshot_id}"),
344 )
345 .await?;
346
347 serde_json::from_value(value)
348 .map_err(|e| eyre::eyre!("Failed to parse evaluated value: {}", e))
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use serde_json::json;
356
357 #[tokio::test]
358 async fn test_rpc_client_creation() {
359 let client = RpcClient::new("http://localhost:3000").await;
360 assert!(client.is_ok() || client.is_err()); }
363
364 #[test]
365 fn test_rpc_params_macro() {
366 let empty_params: Vec<Value> = rpc_params!();
368 let expected: Vec<Value> = vec![];
369 assert_eq!(empty_params, expected);
370
371 let single_param = rpc_params!(42);
373 assert_eq!(single_param, vec![json!(42)]);
374
375 let multi_params = rpc_params!("0x1234567890abcdef", true, 123);
377 assert_eq!(multi_params, vec![json!("0x1234567890abcdef"), json!(true), json!(123)]);
378
379 let address = "0xabcdef1234567890";
381 let recompiled = false;
382 let snapshot_id = 5;
383 let var_params = rpc_params!(address, recompiled, snapshot_id);
384 assert_eq!(var_params, vec![json!(address), json!(recompiled), json!(snapshot_id)]);
385 }
386}