edb_engine/eval/handlers/
edb.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//! EDB handler implementations for the expression evaluator.
18//!
19//! This module provides concrete implementations of all handler traits that work
20//! with EDB's debug snapshot and execution context. The handlers enable real-time
21//! expression evaluation over debug modes (opcode mode and source mode).
22//!
23//! # Key Features
24//!
25//! - **Variable Resolution**: Access local variables, state variables, and `this`
26//! - **Function Calls**: Execute contract functions and EDB pre-compiled functions
27//! - **Storage Access**: Read from contract storage, transient storage, memory, and stack
28//! - **Blockchain Context**: Access `msg`, `tx`, and `block` global variables
29//! - **Cross-Contract Calls**: Function calls and state access on different addresses
30//!
31//! # Pre-compiled Functions (EDB-Version)
32//!
33//! - `edb_sload(address, slot)` - Read storage slot
34//! - `edb_tsload(address, slot)` - Read transient storage (opcode mode only)
35//! - `edb_stack(index)` - Read EVM stack (opcode mode only)
36//! - `edb_memory(offset, size)` - Read EVM memory (opcode mode only)
37//! - `edb_calldata(offset, size)` - Read call data slice
38//! - `keccak256(bytes)` - Compute keccak256 hash
39//! - `edb_help()` - Show help information
40//!
41//! # Usage
42//!
43//! ```rust,ignore
44//! let handlers = EdbHandler::create_handlers(engine_context);
45//! let evaluator = ExpressionEvaluator::new(handlers);
46//! let result = evaluator.eval("balances[msg.sender]", snapshot_id)?;
47//! ```
48
49use std::{collections::HashSet, sync::Arc};
50
51use alloy_dyn_abi::DynSolValue;
52use alloy_primitives::U256;
53use edb_common::types::{parse_callable_abi_entries, CallableAbiEntry, TraceEntry};
54use eyre::{bail, eyre, Result};
55use revm::{database::CacheDB, Database, DatabaseCommit, DatabaseRef};
56use tracing::debug;
57
58use super::*;
59use crate::{EngineContext, Snapshot, SnapshotDetail};
60
61static EDB_EVAL_PLACEHOLDER_MAGIC: &str = "edb_eval_placeholder";
62
63fn from_abi_info(entry: &CallableAbiEntry) -> Option<DynSolValue> {
64    if entry.is_function() {
65        return None; // Only handle non-function entries
66    }
67    let magic = DynSolValue::String(EDB_EVAL_PLACEHOLDER_MAGIC.to_string());
68    let serial_abi = DynSolValue::String(serde_json::to_string(entry).ok()?);
69
70    Some(DynSolValue::Tuple(vec![magic, serial_abi]))
71}
72
73fn into_abi_info(value: &DynSolValue) -> Option<CallableAbiEntry> {
74    if let DynSolValue::Tuple(elements) = value {
75        if elements.len() == 2 {
76            if let DynSolValue::String(magic) = &elements[0] {
77                if magic == EDB_EVAL_PLACEHOLDER_MAGIC {
78                    if let DynSolValue::String(serial_abi) = &elements[1] {
79                        return serde_json::from_str(serial_abi).ok();
80                    }
81                }
82            }
83        }
84    }
85    None
86}
87
88/// EDB-specific handler that uses EdbContext to resolve values
89pub struct EdbHandler<DB>
90where
91    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
92    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
93    <DB as Database>::Error: Clone + Send + Sync,
94{
95    context: Arc<EngineContext<DB>>,
96}
97
98impl<DB> EdbHandler<DB>
99where
100    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
101    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
102    <DB as Database>::Error: Clone + Send + Sync,
103{
104    /// Create a new EDB handler with the given engine context.
105    ///
106    /// # Arguments
107    /// * `context` - The EDB engine context containing snapshots, trace, and recompiled artifacts
108    ///
109    /// # Returns
110    /// A new [`EdbHandler`] instance
111    pub fn new(context: Arc<EngineContext<DB>>) -> Self {
112        Self { context }
113    }
114
115    /// Create all handlers using this EDB context
116    pub fn create_handlers(context: Arc<EngineContext<DB>>) -> EvaluatorHandlers {
117        let handler = Arc::new(Self::new(context));
118
119        EvaluatorHandlers::new()
120            .with_variable_handler(Box::new(EdbVariableHandler(handler.clone())))
121            .with_mapping_array_handler(Box::new(EdbMappingArrayHandler(handler.clone())))
122            .with_function_call_handler(Box::new(EdbFunctionCallHandler(handler.clone())))
123            .with_member_access_handler(Box::new(EdbMemberAccessHandler(handler.clone())))
124            .with_msg_handler(Box::new(EdbMsgHandler(handler.clone())))
125            .with_tx_handler(Box::new(EdbTxHandler(handler.clone())))
126            .with_block_handler(Box::new(EdbBlockHandler(handler.clone())))
127            .with_validation_handler(Box::new(EdbValidationHandler(handler)))
128    }
129}
130
131// Wrapper structs for each handler trait
132
133/// EDB implementation of [`VariableHandler`].
134///
135/// Resolves variable names to their values using debug snapshot context.
136/// Supports local variables, state variables, mappings, arrays, and the special `this` variable.
137#[derive(Clone)]
138pub struct EdbVariableHandler<DB>(Arc<EdbHandler<DB>>)
139where
140    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
141    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
142    <DB as Database>::Error: Clone + Send + Sync;
143
144/// EDB implementation of [`MappingArrayHandler`].
145///
146/// Handles mapping and array access operations by either reading from debug snapshot
147/// data or making EVM calls for storage-based mappings.
148#[derive(Clone)]
149pub struct EdbMappingArrayHandler<DB>(Arc<EdbHandler<DB>>)
150where
151    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
152    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
153    <DB as Database>::Error: Clone + Send + Sync;
154
155/// EDB implementation of [`FunctionCallHandler`].
156///
157/// Executes function calls including:
158/// - Contract functions (view/pure functions)
159/// - EDB pre-compiled functions (`edb_sload`, `edb_stack`, etc.)
160/// - Built-in functions (`keccak256`)
161/// - Cross-contract function calls
162#[derive(Clone)]
163pub struct EdbFunctionCallHandler<DB>(Arc<EdbHandler<DB>>)
164where
165    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
166    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
167    <DB as Database>::Error: Clone + Send + Sync;
168
169/// EDB implementation of [`MemberAccessHandler`].
170///
171/// Handles member access operations on addresses, enabling access to contract
172/// state variables and view functions on different addresses.
173#[derive(Clone)]
174pub struct EdbMemberAccessHandler<DB>(Arc<EdbHandler<DB>>)
175where
176    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
177    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
178    <DB as Database>::Error: Clone + Send + Sync;
179
180/// EDB implementation of [`MsgHandler`].
181///
182/// Provides access to transaction context variables (`msg.sender`, `msg.value`)
183/// from the current debug snapshot's trace entry.
184#[derive(Clone)]
185pub struct EdbMsgHandler<DB>(Arc<EdbHandler<DB>>)
186where
187    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
188    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
189    <DB as Database>::Error: Clone + Send + Sync;
190
191/// EDB implementation of [`TxHandler`].
192///
193/// Provides access to transaction-level context (`tx.origin`) from the root
194/// trace entry of the current debug session.
195#[derive(Clone)]
196pub struct EdbTxHandler<DB>(Arc<EdbHandler<DB>>)
197where
198    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
199    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
200    <DB as Database>::Error: Clone + Send + Sync;
201
202/// EDB implementation of [`BlockHandler`].
203///
204/// Provides access to blockchain context (`block.number`, `block.timestamp`)
205/// from the EDB engine's fork information and block data.
206#[derive(Clone)]
207pub struct EdbBlockHandler<DB>(Arc<EdbHandler<DB>>)
208where
209    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
210    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
211    <DB as Database>::Error: Clone + Send + Sync;
212
213/// EDB implementation of [`ValidationHandler`].
214///
215/// Validates final expression results, ensuring that placeholder values (like ABI info)
216/// are not returned as final results and must be resolved to concrete values.
217#[allow(dead_code)]
218#[derive(Clone)]
219pub struct EdbValidationHandler<DB>(Arc<EdbHandler<DB>>)
220where
221    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
222    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
223    <DB as Database>::Error: Clone + Send + Sync;
224
225// Implement handler traits for each wrapper
226impl<DB> VariableHandler for EdbVariableHandler<DB>
227where
228    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
229    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
230    <DB as Database>::Error: Clone + Send + Sync,
231{
232    fn get_variable_value(&self, name: &str, snapshot_id: usize) -> Result<DynSolValue> {
233        let snapshot = &self
234            .0
235            .context
236            .snapshots
237            .get(snapshot_id)
238            .ok_or_else(|| {
239                eyre::eyre!(
240                    "Snapshot ID {} not found in EdbHandler::get_variable_value",
241                    snapshot_id
242                )
243            })?
244            .1;
245
246        if name == "this" {
247            return Ok(DynSolValue::Address(snapshot.target_address()));
248        }
249
250        let SnapshotDetail::Hook(detail) = &snapshot.detail() else {
251            bail!("Cannot get variable value from opcode snapshot (except this)");
252        };
253
254        // Let's first check whether this could be a local or state variable
255        if let Some(Some(value)) =
256            detail.locals.get(name).or_else(|| detail.state_variables.get(name))
257        {
258            return Ok((**value).clone().into());
259        }
260
261        // Next, it might be a mapping/array variable
262        let bytecode_address = snapshot.bytecode_address();
263        let Some(contract) = self
264            .0
265            .context
266            .recompiled_artifacts
267            .get(&bytecode_address)
268            .and_then(|art| art.contract())
269        else {
270            bail!("No contract found for bytecode address {:?}", bytecode_address);
271        };
272
273        for entry in parse_callable_abi_entries(contract) {
274            if entry.name == name && entry.is_state_variable() {
275                if let Some(value) = from_abi_info(&entry) {
276                    return Ok(value);
277                }
278            }
279        }
280
281        bail!("No value found for name='{}', snapshot_id={}", name, snapshot_id)
282    }
283}
284
285impl<DB> MappingArrayHandler for EdbMappingArrayHandler<DB>
286where
287    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
288    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
289    <DB as Database>::Error: Clone + Send + Sync,
290{
291    fn get_mapping_or_array_value(
292        &self,
293        root: DynSolValue,
294        indices: Vec<DynSolValue>,
295        snapshot_id: usize,
296    ) -> Result<DynSolValue> {
297        if let Some(abi_info) = into_abi_info(&root) {
298            let (_, info) = self.0.context.snapshots.get(snapshot_id).ok_or_else(|| {
299                eyre::eyre!(
300                    "Snapshot ID {} not found in EdbHandler::get_mapping_or_array_value",
301                    snapshot_id
302                )
303            })?;
304            let to = info.target_address();
305
306            self.0.context.call_in_derived_evm(snapshot_id, to, &abi_info.abi, &indices, None)
307        } else {
308            // Handle direct DynSolValue types recursively
309            if indices.is_empty() {
310                return Ok(root);
311            }
312
313            let (first_index, remaining_indices) = indices.split_first().unwrap();
314
315            let next_value = match &root {
316                DynSolValue::Tuple(elements) => {
317                    // For tuples, index should be a uint
318                    match first_index {
319                        DynSolValue::Uint(idx, _) => {
320                            let idx = idx.to::<usize>();
321                            if idx >= elements.len() {
322                                bail!(
323                                    "Index {} out of bounds for tuple with {} elements",
324                                    idx,
325                                    elements.len()
326                                );
327                            }
328                            elements[idx].clone()
329                        }
330                        _ => bail!(
331                            "Invalid index type for tuple access: expected uint, got {:?}",
332                            first_index
333                        ),
334                    }
335                }
336                DynSolValue::Array(elements) => {
337                    // For arrays, index should be a uint
338                    match first_index {
339                        DynSolValue::Uint(idx, _) => {
340                            let idx = idx.to::<usize>();
341                            if idx >= elements.len() {
342                                bail!(
343                                    "Index {} out of bounds for array with {} elements",
344                                    idx,
345                                    elements.len()
346                                );
347                            }
348                            elements[idx].clone()
349                        }
350                        _ => bail!(
351                            "Invalid index type for array access: expected uint, got {:?}",
352                            first_index
353                        ),
354                    }
355                }
356                DynSolValue::FixedArray(elements) => {
357                    // For fixed arrays, index should be a uint
358                    match first_index {
359                        DynSolValue::Uint(idx, _) => {
360                            let idx = idx.to::<usize>();
361                            if idx >= elements.len() {
362                                bail!(
363                                    "Index {} out of bounds for fixed array with {} elements",
364                                    idx,
365                                    elements.len()
366                                );
367                            }
368                            elements[idx].clone()
369                        }
370                        _ => bail!(
371                            "Invalid index type for fixed array access: expected uint, got {:?}",
372                            first_index
373                        ),
374                    }
375                }
376                _ => {
377                    bail!(
378                        "Cannot index into value of type {:?} with index {:?}",
379                        root,
380                        first_index
381                    );
382                }
383            };
384
385            // Recursively handle remaining indices - this is key because next_value might have abi_info
386            self.get_mapping_or_array_value(next_value, remaining_indices.to_vec(), snapshot_id)
387        }
388    }
389}
390
391fn edb_keccak256(bytes: DynSolValue) -> Result<DynSolValue> {
392    match bytes {
393        DynSolValue::Bytes(b) => {
394            let hash = alloy_primitives::keccak256(&b);
395            Ok(DynSolValue::Bytes(hash.to_vec()))
396        }
397        DynSolValue::FixedBytes(b, n_bytes) => {
398            // Change the bytes to a Vec<u8> of length n_bytes
399            if n_bytes > 32 {
400                bail!("FixedBytes length {} exceeds maximum of 32", n_bytes);
401            } else {
402                let mut v = vec![0u8; n_bytes];
403                v.copy_from_slice(&b[..n_bytes]);
404                let hash = alloy_primitives::keccak256(&v);
405                Ok(DynSolValue::Bytes(hash.to_vec()))
406            }
407        }
408        _ => {
409            bail!("Invalid argument to edb_keccak256: expected bytes, got {:?}", bytes);
410        }
411    }
412}
413
414fn edb_sload<DB>(
415    snapshot: &Snapshot<DB>,
416    address: &DynSolValue,
417    slot: &DynSolValue,
418) -> Result<DynSolValue>
419where
420    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
421    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
422    <DB as Database>::Error: Clone + Send + Sync,
423{
424    match (address, slot) {
425        (DynSolValue::Address(address), DynSolValue::Uint(slot, ..)) => {
426            let db = snapshot.db();
427            let cached_storage = db
428                .cache
429                .accounts
430                .get(address)
431                .map(|acc| &acc.storage)
432                .ok_or(eyre!("Account {:?} not found in edb_sload", address))?;
433            let value = cached_storage.get(slot).cloned().unwrap_or_default();
434
435            Ok(DynSolValue::Uint(value, 256))
436        }
437        _ => {
438            bail!(
439                "Invalid arguments to edb_sload: expected (address, u256), got ({:?}, {:?})",
440                address,
441                slot
442            );
443        }
444    }
445}
446
447fn edb_stack<DB>(snapshot: &Snapshot<DB>, index: &DynSolValue) -> Result<DynSolValue>
448where
449    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
450    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
451    <DB as Database>::Error: Clone + Send + Sync,
452{
453    let SnapshotDetail::Opcode(detail) = snapshot.detail() else {
454        bail!("edb_stack can only be called from an opcode snapshot");
455    };
456
457    match index {
458        DynSolValue::Uint(idx, ..) => {
459            let idx = idx.to::<usize>();
460            let stack = &detail.stack;
461            if idx >= stack.len() {
462                bail!("Index {} out of bounds for stack with {} elements", idx, stack.len());
463            }
464            Ok(stack[stack.len() - 1 - idx].into())
465        }
466        _ => {
467            bail!("Invalid argument to edb_stack: expected u256, got {:?}", index);
468        }
469    }
470}
471
472fn edb_tsload<DB>(
473    snapshot: &Snapshot<DB>,
474    address: &DynSolValue,
475    slot: &DynSolValue,
476) -> Result<DynSolValue>
477where
478    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
479    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
480    <DB as Database>::Error: Clone + Send + Sync,
481{
482    let SnapshotDetail::Opcode(detail) = snapshot.detail() else {
483        bail!("edb_tsload can only be called from an opcode snapshot");
484    };
485
486    match (address, slot) {
487        (DynSolValue::Address(addr), DynSolValue::Uint(idx, ..)) => {
488            let value = detail.transient_storage.get(&(*addr, *idx)).cloned().unwrap_or_default();
489            Ok(DynSolValue::Uint(value, 256))
490        }
491        _ => {
492            bail!(
493                "Invalid arguments to edb_tsload: expected (address, u256), got ({:?}, {:?})",
494                address,
495                slot
496            );
497        }
498    }
499}
500
501fn edb_memory<DB>(
502    snapshot: &Snapshot<DB>,
503    offset: &DynSolValue,
504    size: &DynSolValue,
505) -> Result<DynSolValue>
506where
507    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
508    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
509    <DB as Database>::Error: Clone + Send + Sync,
510{
511    let SnapshotDetail::Opcode(detail) = snapshot.detail() else {
512        bail!("edb_memory can only be called from an opcode snapshot");
513    };
514
515    match (offset, size) {
516        (DynSolValue::Uint(off, ..), DynSolValue::Uint(sz, ..)) => {
517            let off = off.to::<usize>();
518            let sz = sz.to::<usize>();
519            let memory = &detail.memory;
520            if off + sz > memory.len() {
521                bail!(
522                    "edb_memory out of bounds: offset {} + size {} > memory length {}",
523                    off,
524                    sz,
525                    memory.len()
526                );
527            }
528            let slice = &memory[off..off + sz];
529            Ok(DynSolValue::Bytes(slice.to_vec()))
530        }
531        _ => {
532            bail!(
533                "Invalid arguments to edb_memory: expected (u256, u256), got ({:?}, {:?})",
534                offset,
535                size
536            );
537        }
538    }
539}
540
541fn edb_calldata(
542    entry: &TraceEntry,
543    offset: &DynSolValue,
544    size: &DynSolValue,
545) -> Result<DynSolValue> {
546    match (offset, size) {
547        (DynSolValue::Uint(off, ..), DynSolValue::Uint(sz, ..)) => {
548            let off = off.to::<usize>();
549            let sz = sz.to::<usize>();
550            let calldata = &entry.input;
551            if off + sz > calldata.len() {
552                bail!(
553                    "edb_calldata out of bounds: offset {} + size {} > calldata length {}",
554                    off,
555                    sz,
556                    calldata.len()
557                );
558            }
559            let slice = &calldata[off..off + sz];
560            Ok(DynSolValue::Bytes(slice.to_vec()))
561        }
562        _ => {
563            bail!(
564                "Invalid arguments to edb_calldata: expected (u256, u256), got ({:?}, {:?})",
565                offset,
566                size
567            );
568        }
569    }
570}
571
572fn edb_help() -> Result<DynSolValue> {
573    let help_text = r#"EDB Expression Evaluator Help
574
575OVERVIEW:
576Real-time expression evaluation over debug modes. Evaluate Solidity-like expressions
577against contract state, variables, and blockchain context at any execution point.
578
579VARIABLES:
580• this                    - Current contract address
581• Local variables         - Access by name (e.g., balance, owner)
582• State variables         - Access by name (e.g., totalSupply, users)
583• Mapping/array variables - Access with bracket notation (e.g., balances[addr])
584
585BLOCKCHAIN CONTEXT:
586• msg.sender     - Transaction sender address
587• msg.value      - Transaction value in wei
588• tx.origin      - Original transaction sender
589• block.number   - Current block number
590• block.timestamp - Current block timestamp
591
592PRE-COMPILED FUNCTIONS (EDB-VERSION):
593• edb_sload(address, slot)      - Read storage slot from address
594• edb_tsload(address, slot)     - Read transient storage (opcode mode only)
595• edb_stack(index)              - Read EVM stack value (opcode mode only)
596• edb_memory(offset, size)      - Read EVM memory (opcode mode only)
597• edb_calldata(offset, size)    - Read call data slice
598• keccak256(bytes)              - Compute keccak256 hash
599• edb_help()                    - Show this help
600
601CONTRACT FUNCTIONS:
602• Call any contract function by name with arguments
603• Access state variables and view functions
604• Member access on addresses (e.g., addr.balanceOf(user))
605• Cross-contract calls (e.g., token.transfer(to, amount))
606• State variable access on different addresses (e.g., addr.owner)
607
608OPERATORS:
609• Arithmetic: +, -, *, /, %, **
610• Comparison: ==, !=, <, <=, >, >=
611• Logical: &&, ||, !
612• Bitwise: &, |, ^, ~, <<, >>
613• Ternary: condition ? true_value : false_value
614
615EXAMPLES:
616• balances[msg.sender]
617• totalSupply() > 1000000
618• block.timestamp - lastUpdate > 3600
619• edb_sload(this, 0x123...)
620• owner == msg.sender && msg.value > 0
621• token.balanceOf(user) * price / 1e18
622• addr.owner == this
623• contractAddr.getUserBalance(msg.sender)
624
625Note: Use 'this' to reference the current contract address in expressions."#;
626
627    Ok(DynSolValue::String(help_text.to_string()))
628}
629
630impl<DB> FunctionCallHandler for EdbFunctionCallHandler<DB>
631where
632    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
633    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
634    <DB as Database>::Error: Clone + Send + Sync,
635{
636    fn call_function(
637        &self,
638        name: &str,
639        args: &[DynSolValue],
640        callee: Option<&DynSolValue>,
641        snapshot_id: usize,
642    ) -> Result<DynSolValue> {
643        debug!(
644            "EdbHandler::call_function name='{}', args={:?}, callee={:?}, snapshot_id={}",
645            name, args, callee, snapshot_id
646        );
647
648        let (frame_id, snapshot) = self.0.context.snapshots.get(snapshot_id).ok_or_else(|| {
649            eyre::eyre!("Snapshot ID {} not found in EdbHandler::call_function", snapshot_id)
650        })?;
651
652        let entry = self.0.context.trace.get(frame_id.trace_entry_id()).ok_or_else(|| {
653            eyre::eyre!("Frame ID {} not found in EdbHandler::call_function", frame_id)
654        })?;
655
656        // Let's first handle our edb-specific pseudo-functions
657        if name == "edb_sload" && args.len() == 2 {
658            return edb_sload(snapshot, &args[0], &args[1]);
659        } else if name == "edb_tsload" && args.len() == 2 {
660            return edb_tsload(snapshot, &args[0], &args[1]);
661        } else if name == "edb_stack" && args.len() == 1 {
662            return edb_stack(snapshot, &args[0]);
663        } else if name == "edb_calldata" && args.len() == 2 {
664            return edb_calldata(entry, &args[0], &args[1]);
665        } else if name == "edb_memory" && args.len() == 2 {
666            return edb_memory(snapshot, &args[0], &args[1]);
667        } else if name == "keccak256" && args.len() == 1 {
668            return edb_keccak256(args[0].clone());
669        } else if name == "edb_help" && args.is_empty() {
670            return edb_help();
671        }
672
673        // Let's then handle calls to functions in the contract's ABI
674        let to = if let Some(v) = callee {
675            match v {
676                DynSolValue::Address(addr) => *addr,
677                _ => {
678                    bail!("Callee must be an address, got {:?}", callee);
679                }
680            }
681        } else {
682            snapshot.target_address()
683        };
684
685        let mut address_candidates = self
686            .0
687            .context
688            .address_code_address_map()
689            .get(&to)
690            .cloned()
691            .unwrap_or_default()
692            .into_iter()
693            .collect::<Vec<_>>();
694        address_candidates.insert(0, to); // Prioritize the direct address
695
696        let mut errors = Vec::new();
697        for address_candidate in address_candidates {
698            if let Some(contract) = self
699                .0
700                .context
701                .recompiled_artifacts
702                .get(&address_candidate)
703                .and_then(|art| art.contract())
704            {
705                for entry in parse_callable_abi_entries(contract) {
706                    if entry.name == name && entry.inputs.len() == args.len() {
707                        match self.0.context.call_in_derived_evm(
708                            snapshot_id,
709                            to,
710                            &entry.abi,
711                            args,
712                            None,
713                        ) {
714                            Ok(result) => return Ok(result),
715                            Err(e) => {
716                                errors.push(e);
717                                debug!(
718                                    "Function '{}' found in contract at address {:?}, but call failed",
719                                    name, address_candidate
720                                );
721                            }
722                        }
723                    }
724                }
725            }
726        }
727
728        if errors.is_empty() {
729            bail!(
730                "No function found for name='{}', args={:?}, callee={:?}, snapshot_id={}",
731                name,
732                args,
733                callee,
734                snapshot_id
735            )
736        } else {
737            let combined_error = errors
738                .into_iter()
739                .map(|e| format!("{e}"))
740                .collect::<HashSet<_>>()
741                .into_iter()
742                .collect::<Vec<_>>()
743                .join(";\n");
744            bail!(
745                "Function call failed for name='{}', args={:?}, callee={:?}, snapshot_id={}:\n{}",
746                name,
747                args,
748                callee,
749                snapshot_id,
750                combined_error
751            )
752        }
753    }
754}
755
756impl<DB> MemberAccessHandler for EdbMemberAccessHandler<DB>
757where
758    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
759    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
760    <DB as Database>::Error: Clone + Send + Sync,
761{
762    fn access_member(
763        &self,
764        value: DynSolValue,
765        member: &str,
766        snapshot_id: usize,
767    ) -> Result<DynSolValue> {
768        if let DynSolValue::Address(addr) = value {
769            let mut address_candidates = self
770                .0
771                .context
772                .address_code_address_map()
773                .get(&addr)
774                .cloned()
775                .unwrap_or_default()
776                .into_iter()
777                .collect::<Vec<_>>();
778            address_candidates.insert(0, addr); // Prioritize the direct address
779
780            for address_candidate in address_candidates {
781                if let Some(contract) = self
782                    .0
783                    .context
784                    .recompiled_artifacts
785                    .get(&address_candidate)
786                    .and_then(|art| art.contract())
787                {
788                    for entry in parse_callable_abi_entries(contract) {
789                        if entry.name == member
790                            && entry.is_state_variable()
791                            && entry.inputs.is_empty()
792                        {
793                            match self.0.context.call_in_derived_evm(
794                                snapshot_id,
795                                addr,
796                                &entry.abi,
797                                &[],
798                                None,
799                            ) {
800                                Ok(result) => return Ok(result),
801                                Err(_e) => {
802                                    debug!(
803                                    "Function '{}' found in contract at address {:?}, but call failed",
804                                    member, address_candidate
805                                );
806                                }
807                            }
808                        }
809                    }
810                }
811            }
812        }
813
814        bail!(
815            "Invalid member access for value={:?}, member='{}', snapshot_id={}",
816            value,
817            member,
818            snapshot_id
819        )
820    }
821}
822
823impl<DB> MsgHandler for EdbMsgHandler<DB>
824where
825    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
826    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
827    <DB as Database>::Error: Clone + Send + Sync,
828{
829    fn get_msg_sender(&self, snapshot_id: usize) -> Result<DynSolValue> {
830        let (frame_id, _) = self.0.context.snapshots.get(snapshot_id).ok_or_else(|| {
831            eyre::eyre!("Snapshot ID {} not found in EdbHandler::get_msg_sender", snapshot_id)
832        })?;
833
834        let entry = self.0.context.trace.get(frame_id.trace_entry_id()).ok_or_else(|| {
835            eyre::eyre!("Frame ID {} not found in EdbHandler::get_msg_sender", frame_id)
836        })?;
837
838        Ok(DynSolValue::Address(entry.caller))
839    }
840
841    fn get_msg_value(&self, snapshot_id: usize) -> Result<DynSolValue> {
842        let (frame_id, _) = self.0.context.snapshots.get(snapshot_id).ok_or_else(|| {
843            eyre::eyre!("Snapshot ID {} not found in EdbHandler::get_msg_value", snapshot_id)
844        })?;
845
846        let entry = self.0.context.trace.get(frame_id.trace_entry_id()).ok_or_else(|| {
847            eyre::eyre!("Frame ID {} not found in EdbHandler::get_msg_value", frame_id)
848        })?;
849
850        Ok(DynSolValue::Uint(entry.value, 256))
851    }
852}
853
854impl<DB> TxHandler for EdbTxHandler<DB>
855where
856    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
857    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
858    <DB as Database>::Error: Clone + Send + Sync,
859{
860    fn get_tx_origin(&self, _snapshot_id: usize) -> Result<DynSolValue> {
861        let entry = self
862            .0
863            .context
864            .trace
865            .first()
866            .ok_or_else(|| eyre::eyre!("No trace entry found in EdbHandler::get_tx_origin"))?;
867
868        Ok(DynSolValue::Address(entry.caller))
869    }
870}
871
872impl<DB> BlockHandler for EdbBlockHandler<DB>
873where
874    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
875    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
876    <DB as Database>::Error: Clone + Send + Sync,
877{
878    fn get_block_number(&self, _snapshot_id: usize) -> Result<DynSolValue> {
879        Ok(DynSolValue::Uint(U256::from(self.0.context.fork_info.block_number), 256))
880    }
881
882    fn get_block_timestamp(&self, _snapshot_id: usize) -> Result<DynSolValue> {
883        Ok(DynSolValue::Uint(self.0.context.block.timestamp, 256))
884    }
885}
886
887impl<DB> ValidationHandler for EdbValidationHandler<DB>
888where
889    DB: Database + DatabaseCommit + DatabaseRef + Clone + Send + Sync + 'static,
890    <CacheDB<DB> as Database>::Error: Clone + Send + Sync,
891    <DB as Database>::Error: Clone + Send + Sync,
892{
893    fn validate_value(&self, value: DynSolValue) -> Result<DynSolValue> {
894        if into_abi_info(&value).is_some() {
895            bail!("Mapping or array value cannot be directly returned; please access a member or call a function to get a concrete value");
896        } else {
897            Ok(value)
898        }
899    }
900}