Skip to main content

edb_tui/
rpc.rs

1// EDB - Ethereum Debugger
2// Copyright (C) 2024 Zhuo Zhang and Wuqi Zhang
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! RPC client for communicating with the debug server
18//!
19//! This module provides a client for making JSON-RPC calls to the debug server.
20
21use 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
38/// Macro for building RPC parameters with any number of arguments
39///
40/// # Examples
41/// ```ignore
42/// let params = rpc_params!(); // Empty params
43/// let params = rpc_params!(42); // Single parameter
44/// let params = rpc_params!("0x123...", true); // Multiple parameters of different types
45/// let params = rpc_params!(snapshot_id, address, is_recompiled); // Variable references
46/// ```
47macro_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/// RPC client for debug server communication
58#[derive(Debug)]
59pub struct RpcClient {
60    client: HttpClient,
61    server_url: String,
62    /// Shared spinner state for loading indication
63    spinner: Arc<RwLock<Spinner>>,
64}
65
66impl RpcClient {
67    /// Create a new RPC client
68    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    /// Test connection to a server URL
82    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        // Try a simple health check or method call
90        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    /// Generic method to make RPC requests with automatic spinner management
103    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    /// Get server URL
128    pub fn server_url(&self) -> &str {
129        &self.server_url
130    }
131
132    /// Get shared reference to spinner for UI updates
133    pub fn spinner(&self) -> Arc<RwLock<Spinner>> {
134        Arc::clone(&self.spinner)
135    }
136
137    /// Check if spinner is currently loading
138    pub fn is_loading(&self) -> bool {
139        self.spinner.read().unwrap().is_loading()
140    }
141
142    /// Get spinner display text
143    pub fn spinner_display(&self) -> String {
144        self.spinner.read().unwrap().display_text()
145    }
146
147    /// Start loading spinner for an operation
148    fn start_loading(&self, operation: &str) {
149        self.spinner.write().unwrap().start_loading(operation);
150        debug!("Started loading spinner: {}", operation);
151    }
152
153    /// Finish loading spinner
154    fn finish_loading(&self) {
155        self.spinner.write().unwrap().finish_loading();
156        debug!("Finished loading spinner");
157    }
158
159    /// Update spinner animation (call from render loop)
160    pub fn tick(&self) {
161        self.spinner.write().unwrap().tick();
162    }
163
164    /// Check server health
165    pub async fn health_check(&self) -> Result<Value> {
166        debug!("Checking server health");
167
168        // Make a simple HTTP GET request to the health endpoint
169        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
179// Rpc methods
180impl RpcClient {
181    /// Get execution trace
182    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    /// Get contract abi
190    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    /// Get callable abi info
208    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    /// Get contract constructor arguments
222    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    /// Get total snapshot count
236    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    /// Get snapshot information
250    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    /// Get code
264    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    /// Get next call
277    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    /// Get prev call
290    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    /// Get storage value at a given slot
303    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    /// Get storage diff
317    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    /// Evaluate expression on a given snapshot
334    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        // This would fail without a running server, but we can test creation logic
361        assert!(client.is_ok() || client.is_err()); // Either is fine for this test
362    }
363
364    #[test]
365    fn test_rpc_params_macro() {
366        // Test empty params
367        let empty_params: Vec<Value> = rpc_params!();
368        let expected: Vec<Value> = vec![];
369        assert_eq!(empty_params, expected);
370
371        // Test single parameter
372        let single_param = rpc_params!(42);
373        assert_eq!(single_param, vec![json!(42)]);
374
375        // Test multiple parameters of different types
376        let multi_params = rpc_params!("0x1234567890abcdef", true, 123);
377        assert_eq!(multi_params, vec![json!("0x1234567890abcdef"), json!(true), json!(123)]);
378
379        // Test with variables
380        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}