Skip to main content

tycho_simulation/evm/
simulation.rs

1use std::{clone::Clone, collections::HashMap, default::Default, env, fmt::Debug};
2
3use alloy::primitives::{Address, Bytes, U256};
4use revm::{
5    context::{
6        result::{EVMError, ExecutionResult, Output, ResultAndState},
7        BlockEnv, CfgEnv, Context, TxEnv,
8    },
9    context_interface::JournalTr,
10    interpreter::{return_ok, InstructionResult},
11    primitives::{hardfork::SpecId, TxKind},
12    state::EvmState,
13    DatabaseRef, ExecuteEvm, InspectEvm, MainBuilder, MainContext,
14};
15use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig};
16use strum_macros::Display;
17use tokio::runtime::{Handle, Runtime};
18use tracing::debug;
19use tycho_common::models::contract::AccountDelta;
20
21use super::{
22    account_storage::StateUpdate,
23    traces::{handle_traces, TraceResult},
24};
25use crate::evm::engine_db::{
26    engine_db_interface::EngineDatabaseInterface, simulation_db::OverriddenSimulationDB,
27};
28
29/// An error representing any transaction simulation result other than successful execution
30#[derive(Debug, Display, Clone, PartialEq)]
31pub enum SimulationEngineError {
32    /// Something went wrong while getting storage; might be caused by network issues.
33    /// Retrying may help.
34    StorageError(String),
35    /// Gas limit has been reached. Retrying while increasing gas limit or waiting for a gas price
36    /// reduction may help.
37    OutOfGas(String, String),
38    /// Simulation didn't succeed; likely not related to network or gas, so retrying won't help
39    TransactionError { data: String, gas_used: Option<u64> },
40    /// Processing traces failed.
41    TraceError(String),
42}
43
44/// A result of a successful transaction simulation
45#[derive(Debug, Clone, Default)]
46pub struct SimulationResult {
47    /// Output of transaction execution as bytes
48    pub result: Bytes,
49    /// State changes caused by the transaction
50    pub state_updates: HashMap<Address, StateUpdate>,
51    /// Gas used by the transaction (already reduced by the refunded gas)
52    pub gas_used: u64,
53    /// Transient storage changes captured during the simulation
54    pub transient_storage: HashMap<Address, HashMap<U256, U256>>,
55}
56
57/// Simulation engine
58#[derive(Debug, Clone)]
59pub struct SimulationEngine<D: EngineDatabaseInterface + Clone + Debug>
60where
61    <D as DatabaseRef>::Error: Debug,
62    <D as EngineDatabaseInterface>::Error: Debug,
63{
64    pub state: D,
65    pub trace: bool,
66}
67
68impl<D: EngineDatabaseInterface + Clone + Debug> SimulationEngine<D>
69where
70    <D as DatabaseRef>::Error: Debug,
71    <D as EngineDatabaseInterface>::Error: Debug,
72{
73    /// Create a new simulation engine
74    ///
75    /// # Arguments
76    ///
77    /// * `state` - Database reference to be used for simulation
78    /// * `trace` - Whether to print the entire execution trace
79    ///
80    /// # Notes
81    /// If you set traces to true, consider setting the ETHERSCAN_API_KEY env variable
82    /// so the tracer can pull contract metadata from etherscan.
83    pub fn new(state: D, trace: bool) -> Self {
84        Self { state, trace }
85    }
86
87    /// Simulate a transaction
88    ///
89    /// State's block will be modified to be the last block before the simulation's block.
90    pub fn simulate(
91        &self,
92        params: &SimulationParameters,
93    ) -> Result<SimulationResult, SimulationEngineError> {
94        // We allocate a new EVM so we can work with a simple referenced DB instead of a fully
95        // concurrently save shared reference and write locked object. Note that concurrently
96        // calling this method is therefore not possible.
97        // There is no need to keep an EVM on the struct as it only holds the environment and the
98        // db, the db is simply a reference wrapper. To avoid lifetimes leaking we don't let the evm
99        // struct outlive this scope.
100
101        // Borrowed, not cloned: an override map can hold every storage slot a pending block
102        // touched, and this runs once per pool.
103        let no_storage_overrides = HashMap::new();
104        let no_balance_overrides = HashMap::new();
105        let db_ref = OverriddenSimulationDB {
106            inner_db: &self.state,
107            overrides: params
108                .overrides
109                .as_ref()
110                .unwrap_or(&no_storage_overrides),
111            native_balance_overrides: params
112                .native_balance_overrides
113                .as_ref()
114                .unwrap_or(&no_balance_overrides),
115        };
116
117        let tx_env = TxEnv {
118            caller: params.caller,
119            gas_limit: params.gas_limit.unwrap_or(8_000_000),
120            kind: TxKind::Call(params.to),
121            value: params.value,
122            data: Bytes::copy_from_slice(&params.data),
123            ..Default::default()
124        };
125
126        let mut block =
127            self.state
128                .get_current_block()
129                .ok_or(SimulationEngineError::StorageError(
130                    "Current block not set in SimulationEngine.".into(),
131                ))?;
132
133        if let Some(overrides) = &params.block_overrides {
134            if let Some(number) = overrides.number {
135                block.number = number;
136            }
137            if let Some(timestamp) = overrides.timestamp {
138                block.timestamp = timestamp;
139            }
140        }
141
142        let block_env = BlockEnv {
143            number: U256::from(block.number),
144            timestamp: U256::from(block.timestamp),
145            ..Default::default()
146        };
147
148        let mut cfg_env: CfgEnv<SpecId> = CfgEnv::new_with_spec(SpecId::PRAGUE);
149        cfg_env.disable_nonce_check = true;
150        cfg_env.disable_eip3607 = true;
151
152        let context = Context::mainnet()
153            .with_cfg(cfg_env)
154            .with_ref_db(db_ref)
155            .with_block(block_env)
156            .with_tx(tx_env.clone())
157            .modify_journal_chained(|journal| {
158                if let Some(transient_storage) = params.transient_storage.clone() {
159                    for (address, slots) in transient_storage {
160                        for (slot, value) in slots {
161                            journal.tstore(address, slot, value);
162                        }
163                    }
164                }
165                if let Some(overrides) = &params.overrides {
166                    for (address, storage) in overrides {
167                        let keys = storage.keys().copied();
168                        let _ = journal.warm_account_and_storage(*address, keys);
169                    }
170                }
171            });
172
173        let evm_result = if self.trace {
174            let mut tracer = TracingInspector::new(TracingInspectorConfig::default());
175
176            let res = {
177                let mut vm = context.build_mainnet_with_inspector(&mut tracer);
178
179                debug!(
180                    "Starting simulation with tx parameters: {:#?} {:#?}",
181                    vm.ctx.tx, vm.ctx.block
182                );
183                vm.inspect_tx(tx_env.clone())
184            };
185
186            Self::print_traces(tracer, res.as_ref().ok())?;
187
188            res
189        } else {
190            let mut vm = context.build_mainnet();
191
192            debug!("Starting simulation with tx parameters: {:#?} {:#?}", vm.ctx.tx, vm.ctx.block);
193
194            vm.replay()
195        };
196
197        // TODO: update revm to 25.0.0 and get transient storage from the journaled state
198        interpret_evm_result(evm_result, HashMap::new())
199    }
200
201    pub fn clear_temp_storage(&mut self) -> Result<(), <D as EngineDatabaseInterface>::Error> {
202        self.state.clear_temp_storage()
203    }
204
205    fn print_traces(
206        tracer: TracingInspector,
207        res: Option<&ResultAndState>,
208    ) -> Result<(), SimulationEngineError> {
209        let (exit_reason, _gas_refunded, gas_used, _out, _exec_logs) = match res {
210            Some(ResultAndState { result, state: _ }) => {
211                // let ResultAndState { result, state: _ } = res;
212                match result.clone() {
213                    ExecutionResult::Success {
214                        reason,
215                        gas_used,
216                        gas_refunded,
217                        output,
218                        logs,
219                        ..
220                    } => (reason.into(), gas_refunded, gas_used, Some(output), logs),
221                    ExecutionResult::Revert { gas_used, output } => {
222                        // Need to fetch the unused gas
223                        (
224                            InstructionResult::Revert,
225                            0_u64,
226                            gas_used,
227                            Some(Output::Call(output)),
228                            vec![],
229                        )
230                    }
231                    ExecutionResult::Halt { reason, gas_used } => {
232                        (reason.into(), 0_u64, gas_used, None, vec![])
233                    }
234                }
235            }
236            _ => (InstructionResult::Stop, 0_u64, 0, None, vec![]),
237        };
238
239        let trace_res = TraceResult {
240            success: matches!(exit_reason, return_ok!()),
241            traces: Some(vec![tracer.into_traces()]),
242            gas_used,
243        };
244
245        tokio::task::block_in_place(|| -> Result<(), SimulationEngineError> {
246            let future = async {
247                handle_traces(
248                    trace_res,
249                    env::var("ETHERSCAN_API_KEY").ok(),
250                    tycho_common::models::Chain::Ethereum,
251                )
252                .await
253                .map_err(|err| SimulationEngineError::TraceError(err.to_string()))
254            };
255            if let Ok(handle) = Handle::try_current() {
256                // If successful, use the existing runtime to block on the future
257                handle.block_on(future)
258            } else {
259                // If no runtime is found, create a new one and block on the future
260                let rt = Runtime::new().map_err(|err| {
261                    SimulationEngineError::TraceError(format!(
262                        "Failed to create a new runtime: {err}"
263                    ))
264                })?;
265                rt.block_on(future)
266            }
267        })?;
268
269        Ok(())
270    }
271}
272
273/// Convert a complex EVMResult into a simpler structure
274///
275/// EVMResult is not of an error type even if the transaction was not successful.
276/// This function returns an Ok if and only if the transaction was successful.
277/// In case the transaction was reverted, halted, or another error occurred (like an error
278/// when accessing storage), this function returns an Err with a simple String description
279/// of an underlying cause.
280///
281/// # Arguments
282///
283/// * `evm_result` - output from calling `revm.transact()`
284///
285/// # Errors
286///
287/// * `SimulationError` - simulation wasn't successful for any reason. See variants for details.
288fn interpret_evm_result<DBError: Debug>(
289    evm_result: Result<ResultAndState, EVMError<DBError>>,
290    transient_storage: HashMap<Address, HashMap<U256, U256>>,
291) -> Result<SimulationResult, SimulationEngineError> {
292    match evm_result {
293        Ok(result_and_state) => match result_and_state.result {
294            ExecutionResult::Success { gas_used, gas_refunded, output, .. } => {
295                Ok(interpret_evm_success(
296                    gas_used,
297                    gas_refunded,
298                    output,
299                    result_and_state.state,
300                    transient_storage,
301                ))
302            }
303            ExecutionResult::Revert { output, gas_used } => {
304                Err(SimulationEngineError::TransactionError {
305                    data: format!("0x{encoded}", encoded = hex::encode::<Vec<u8>>(output.into())),
306                    gas_used: Some(gas_used),
307                })
308            }
309            ExecutionResult::Halt { reason, gas_used } => {
310                Err(SimulationEngineError::TransactionError {
311                    data: format!("{reason:?}"),
312                    gas_used: Some(gas_used),
313                })
314            }
315        },
316        Err(evm_error) => match evm_error {
317            EVMError::Transaction(invalid_tx) => Err(SimulationEngineError::TransactionError {
318                data: format!("EVM error: {invalid_tx:?}"),
319                gas_used: None,
320            }),
321            EVMError::Database(db_error) => {
322                Err(SimulationEngineError::StorageError(format!("Storage error: {db_error:?}")))
323            }
324            EVMError::Custom(err) => Err(SimulationEngineError::TransactionError {
325                data: format!("Unexpected error {err}"),
326                gas_used: None,
327            }),
328            EVMError::Header(err) => Err(SimulationEngineError::TransactionError {
329                data: format!("Unexpected error {err}"),
330                gas_used: None,
331            }),
332        },
333    }
334}
335
336// Helper function to extract some details from a successful transaction execution
337fn interpret_evm_success(
338    gas_used: u64,
339    gas_refunded: u64,
340    output: Output,
341    state: EvmState,
342    transient_storage: HashMap<Address, HashMap<U256, U256>>,
343) -> SimulationResult {
344    SimulationResult {
345        result: output.into_data(),
346        state_updates: {
347            // For each account mentioned in state updates in REVM output, we will have
348            // one record in our hashmap. Such record contains *new* values of account's
349            // state. This record's optional `storage` field will contain
350            // account's storage changes (as a hashmap from slot index to slot value),
351            // unless REVM output doesn't contain any storage for this account, in which case
352            // we set this field to None. If REVM did return storage, we return one record
353            // per *modified* slot (sometimes REVM returns a storage record for an account
354            // even if the slots are not modified).
355            let mut account_updates: HashMap<Address, StateUpdate> = HashMap::new();
356            for (address, account) in state {
357                account_updates.insert(
358                    address,
359                    StateUpdate {
360                        // revm doesn't say if the balance was actually changed
361                        balance: Some(account.info.balance),
362                        // revm doesn't say if the code was actually changed
363                        storage: {
364                            if account.storage.is_empty() {
365                                None
366                            } else {
367                                let mut slot_updates: HashMap<U256, U256> = HashMap::new();
368                                for (index, slot) in account.storage {
369                                    if slot.is_changed() {
370                                        slot_updates.insert(index, slot.present_value);
371                                    }
372                                }
373                                if slot_updates.is_empty() {
374                                    None
375                                } else {
376                                    Some(slot_updates)
377                                }
378                            }
379                        },
380                    },
381                );
382            }
383            account_updates
384        },
385        gas_used: gas_used - gas_refunded,
386        transient_storage,
387    }
388}
389
390#[derive(Debug, Default)]
391/// Data needed to invoke a transaction simulation
392pub struct SimulationParameters {
393    /// Address of the sending account
394    pub caller: Address,
395    /// Address of the receiving account/contract
396    pub to: Address,
397    /// Calldata
398    pub data: Vec<u8>,
399    /// Amount of native token sent
400    pub value: U256,
401    /// EVM state overrides.
402    /// Will be merged with existing state. Will take effect only for current simulation.
403    pub overrides: Option<HashMap<Address, HashMap<U256, U256>>>,
404    /// Limit of gas to be used by the transaction
405    pub gas_limit: Option<u64>,
406    /// Map of the address whose transient storage will be overwritten, to a map of storage slot
407    /// and value.
408    pub transient_storage: Option<HashMap<Address, HashMap<U256, U256>>>,
409    /// Per-call block context overrides.
410    pub block_overrides: Option<BlockEnvOverrides>,
411    /// Native balance overrides. Same per-call scoping as `overrides`.
412    pub native_balance_overrides: Option<HashMap<Address, U256>>,
413}
414
415#[derive(Debug, Clone, Default, PartialEq, Eq)]
416pub struct BlockEnvOverrides {
417    pub number: Option<u64>,
418    pub timestamp: Option<u64>,
419}
420
421/// State a view call runs against, overriding what the engine's database holds.
422///
423/// `Default` means no overrides, so a call reads the engine's confirmed state.
424#[derive(Debug, Clone, Default)]
425pub struct PendingOverrides {
426    pub storage: Option<HashMap<Address, HashMap<U256, U256>>>,
427    pub native_balances: Option<HashMap<Address, U256>>,
428    pub block: Option<BlockEnvOverrides>,
429}
430
431impl PendingOverrides {
432    /// The state `accounts` would leave behind, under the `block` environment.
433    ///
434    /// Only the accounts given appear, so every slot they did not write keeps its confirmed
435    /// value. A cleared slot is stored as `None` and reads back as zero, which is what the EVM
436    /// would see. Values shorter than 32 bytes are left-padded, which is how the indexer stores
437    /// slots and balances with leading zeros trimmed.
438    pub fn from_account_deltas(
439        accounts: &HashMap<tycho_common::Bytes, AccountDelta>,
440        block: Option<BlockEnvOverrides>,
441    ) -> Self {
442        let mut storage: HashMap<Address, HashMap<U256, U256>> = HashMap::new();
443        let mut native_balances: HashMap<Address, U256> = HashMap::new();
444        for (address, delta) in accounts {
445            if address.len() != 20 {
446                continue;
447            }
448            let address = Address::from_slice(address.as_ref());
449            let slots: HashMap<U256, U256> = delta
450                .slots
451                .iter()
452                .map(|(slot, value)| {
453                    (
454                        U256::from_be_slice(slot.as_ref()),
455                        value
456                            .as_ref()
457                            .map_or(U256::ZERO, |v| U256::from_be_slice(v.as_ref())),
458                    )
459                })
460                .collect();
461            if !slots.is_empty() {
462                storage.insert(address, slots);
463            }
464            if let Some(balance) = &delta.balance {
465                native_balances.insert(address, U256::from_be_slice(balance.as_ref()));
466            }
467        }
468        PendingOverrides {
469            storage: (!storage.is_empty()).then_some(storage),
470            native_balances: (!native_balances.is_empty()).then_some(native_balances),
471            block,
472        }
473    }
474
475    /// Parameters for a `data` view call to `to` from the zero address, under these overrides.
476    ///
477    /// Clones the override maps, which `SimulationParameters` owns. Callers that issue many
478    /// calls per pending block pay that clone per call.
479    pub fn view_call(&self, to: Address, data: Vec<u8>) -> SimulationParameters {
480        SimulationParameters {
481            caller: Address::ZERO,
482            to,
483            data,
484            overrides: self.storage.clone(),
485            native_balance_overrides: self.native_balances.clone(),
486            block_overrides: self.block.clone(),
487            ..Default::default()
488        }
489    }
490}
491
492#[cfg(test)]
493mod pending_overrides_tests {
494    use std::collections::HashMap;
495
496    use alloy::primitives::{Address, U256};
497    use tycho_common::{
498        models::{contract::AccountDelta, Chain, ChangeType},
499        Bytes,
500    };
501
502    use super::{BlockEnvOverrides, PendingOverrides};
503
504    fn account_delta(
505        address: [u8; 20],
506        slots: Vec<(Vec<u8>, Option<Vec<u8>>)>,
507        balance: Option<Vec<u8>>,
508    ) -> (Bytes, AccountDelta) {
509        let address = Bytes::from(address.to_vec());
510        (
511            address.clone(),
512            AccountDelta::new(
513                Chain::Ethereum,
514                address,
515                slots
516                    .into_iter()
517                    .map(|(slot, value)| (Bytes::from(slot), value.map(Bytes::from)))
518                    .collect(),
519                balance.map(Bytes::from),
520                None,
521                ChangeType::Update,
522            ),
523        )
524    }
525
526    #[test]
527    fn test_slots_and_balances_reach_the_overrides() {
528        let account = [0xaau8; 20];
529        let block = BlockEnvOverrides { number: Some(3), timestamp: Some(4) };
530        let overrides = PendingOverrides::from_account_deltas(
531            &HashMap::from([account_delta(
532                account,
533                vec![(vec![3], Some(vec![7]))],
534                Some(vec![0x0d, 0xe0]),
535            )]),
536            Some(block.clone()),
537        );
538
539        let address = Address::from_slice(&account);
540        assert_eq!(
541            overrides
542                .storage
543                .as_ref()
544                .and_then(|s| s.get(&address))
545                .and_then(|slots| slots.get(&U256::from(3))),
546            Some(&U256::from(7)),
547            "a written slot must be overridden"
548        );
549        assert_eq!(
550            overrides
551                .native_balances
552                .as_ref()
553                .and_then(|b| b.get(&address)),
554            Some(&U256::from(0x0de0)),
555            "a native balance must be overridden"
556        );
557        assert_eq!(overrides.block, Some(block));
558    }
559
560    #[test]
561    fn test_cleared_slot_reads_as_zero() {
562        let account = [0xbbu8; 20];
563        let overrides = PendingOverrides::from_account_deltas(
564            &HashMap::from([account_delta(account, vec![(vec![1], None)], None)]),
565            None,
566        );
567
568        assert_eq!(
569            overrides
570                .storage
571                .as_ref()
572                .and_then(|s| s.get(&Address::from_slice(&account)))
573                .and_then(|slots| slots.get(&U256::from(1))),
574            Some(&U256::ZERO),
575            "a cleared slot must override to zero, not vanish"
576        );
577        assert!(overrides.native_balances.is_none());
578    }
579
580    #[test]
581    fn test_no_accounts_means_no_overrides() {
582        let overrides = PendingOverrides::from_account_deltas(&HashMap::new(), None);
583        assert!(overrides.storage.is_none());
584        assert!(overrides.native_balances.is_none());
585        assert!(overrides.block.is_none());
586    }
587
588    /// Every override a caller sets must reach the parameters, or a pool would silently be
589    /// priced against confirmed state.
590    #[test]
591    fn test_view_call_carries_every_override() {
592        let account = Address::repeat_byte(7);
593        let overrides = PendingOverrides {
594            storage: Some(HashMap::from([(account, HashMap::from([(U256::ZERO, U256::from(1))]))])),
595            native_balances: Some(HashMap::from([(account, U256::from(2))])),
596            block: Some(BlockEnvOverrides { number: Some(3), timestamp: Some(4) }),
597        };
598
599        let params = overrides.view_call(account, vec![0xab]);
600
601        assert_eq!(params.overrides, overrides.storage);
602        assert_eq!(params.native_balance_overrides, overrides.native_balances);
603        assert_eq!(params.block_overrides, overrides.block);
604        assert_eq!(params.caller, Address::ZERO, "A view call must not impersonate an account.");
605        assert_eq!(params.to, account);
606        assert_eq!(params.data, vec![0xab]);
607        assert_eq!(params.value, U256::ZERO, "A view call must not transfer value.");
608    }
609
610    #[test]
611    fn test_default_view_call_overrides_nothing() {
612        let params = PendingOverrides::default().view_call(Address::ZERO, Vec::new());
613
614        assert!(params.overrides.is_none());
615        assert!(params
616            .native_balance_overrides
617            .is_none());
618        assert!(params.block_overrides.is_none());
619    }
620}
621
622#[cfg(test)]
623mod tests {
624    use std::{error::Error, str::FromStr, time::Instant};
625
626    use alloy::{
627        primitives::{Address, Bytes, Keccak256, B256},
628        sol_types::SolValue,
629        transports::{RpcError, TransportError, TransportErrorKind},
630    };
631    use revm::{
632        context::result::{HaltReason, InvalidTransaction, OutOfGasError, SuccessReason},
633        state::{
634            Account, AccountInfo, AccountStatus, Bytecode, EvmState as rState, EvmStorageSlot,
635        },
636    };
637    use tycho_client::feed::BlockHeader;
638    use tycho_common::simulation::errors::SimulationError;
639
640    use super::*;
641    use crate::evm::engine_db::{
642        engine_db_interface::EngineDatabaseInterface,
643        simulation_db::{EVMProvider, SimulationDB},
644        tycho_db::PreCachedDB,
645        utils::{get_client, get_runtime},
646    };
647
648    #[test]
649    fn test_interpret_result_ok_success() {
650        let evm_result: Result<ResultAndState, EVMError<TransportError>> = Ok(ResultAndState {
651            result: ExecutionResult::Success {
652                reason: SuccessReason::Return,
653                gas_used: 100_u64,
654                gas_refunded: 10_u64,
655                logs: Vec::new(),
656                output: Output::Call(Bytes::from_static(b"output")),
657            },
658            state: [(
659                // storage has changed
660                Address::ZERO,
661                Account {
662                    info: AccountInfo {
663                        balance: U256::from_limbs([1, 0, 0, 0]),
664                        nonce: 2,
665                        code_hash: B256::ZERO,
666                        code: None,
667                    },
668                    transaction_id: 0,
669                    storage: [
670                        // this slot has changed
671                        (
672                            U256::from_limbs([3, 1, 0, 0]),
673                            EvmStorageSlot {
674                                original_value: U256::from_limbs([4, 0, 0, 0]),
675                                present_value: U256::from_limbs([5, 0, 0, 0]),
676                                transaction_id: 0,
677                                is_cold: true,
678                            },
679                        ),
680                        // this slot hasn't changed
681                        (
682                            U256::from_limbs([3, 2, 0, 0]),
683                            EvmStorageSlot {
684                                original_value: U256::from_limbs([4, 0, 0, 0]),
685                                present_value: U256::from_limbs([4, 0, 0, 0]),
686                                transaction_id: 0,
687                                is_cold: true,
688                            },
689                        ),
690                    ]
691                    .iter()
692                    .cloned()
693                    .collect(),
694                    status: AccountStatus::Touched,
695                },
696            )]
697            .iter()
698            .cloned()
699            .collect(),
700        });
701
702        let transient_storage = HashMap::from([(
703            Address::from_str("0x1f98400000000000000000000000000000000004").unwrap(),
704            HashMap::from([(U256::from(0), U256::from(1))]),
705        )]);
706        let result = interpret_evm_result(evm_result, transient_storage.clone());
707        let simulation_result = result.unwrap();
708
709        assert_eq!(simulation_result.result, Bytes::from_static(b"output"));
710        let expected_state_updates = [(
711            Address::ZERO,
712            StateUpdate {
713                storage: Some(
714                    [(U256::from_limbs([3, 1, 0, 0]), U256::from_limbs([5, 0, 0, 0]))]
715                        .iter()
716                        .cloned()
717                        .collect(),
718                ),
719                balance: Some(U256::from_limbs([1, 0, 0, 0])),
720            },
721        )]
722        .iter()
723        .cloned()
724        .collect();
725        assert_eq!(simulation_result.state_updates, expected_state_updates);
726        assert_eq!(simulation_result.gas_used, 90);
727        assert_eq!(simulation_result.transient_storage, transient_storage);
728    }
729
730    #[test]
731    fn test_interpret_result_ok_revert() {
732        let evm_result: Result<ResultAndState, EVMError<TransportError>> = Ok(ResultAndState {
733            result: ExecutionResult::Revert {
734                gas_used: 100_u64,
735                output: Bytes::from_static(b"output"),
736            },
737            state: rState::default(),
738        });
739
740        let result = interpret_evm_result(evm_result, HashMap::new());
741
742        assert!(result.is_err());
743        let err = result.err().unwrap();
744        match err {
745            SimulationEngineError::TransactionError { data: _, gas_used } => {
746                assert_eq!(
747                    format!("0x{}", hex::encode::<Vec<u8>>("output".into())),
748                    "0x6f7574707574"
749                );
750                assert_eq!(gas_used, Some(100));
751            }
752            _ => panic!("Wrong type of SimulationError!"),
753        }
754    }
755
756    #[test]
757    fn test_interpret_result_ok_halt() {
758        let evm_result: Result<ResultAndState, EVMError<TransportError>> = Ok(ResultAndState {
759            result: ExecutionResult::Halt {
760                reason: HaltReason::OutOfGas(OutOfGasError::Basic),
761                gas_used: 100_u64,
762            },
763            state: rState::default(),
764        });
765
766        let result = interpret_evm_result(evm_result, HashMap::new());
767
768        assert!(result.is_err());
769        let err = result.err().unwrap();
770        match err {
771            SimulationEngineError::TransactionError { data, gas_used } => {
772                assert_eq!(data, "OutOfGas(Basic)");
773                assert_eq!(gas_used, Some(100));
774            }
775            _ => panic!("Wrong type of SimulationError!"),
776        }
777    }
778
779    #[test]
780    fn test_interpret_result_err_invalid_transaction() {
781        let evm_result: Result<ResultAndState, EVMError<TransportError>> =
782            Err(EVMError::Transaction(InvalidTransaction::PriorityFeeGreaterThanMaxFee));
783
784        let result = interpret_evm_result(evm_result, HashMap::new());
785
786        assert!(result.is_err());
787        let err = result.err().unwrap();
788        match err {
789            SimulationEngineError::TransactionError { data, gas_used } => {
790                assert_eq!(data, "EVM error: PriorityFeeGreaterThanMaxFee");
791                assert_eq!(gas_used, None);
792            }
793            _ => panic!("Wrong type of SimulationError!"),
794        }
795    }
796
797    #[test]
798    fn test_interpret_result_err_db_error() {
799        let evm_result: Result<ResultAndState, EVMError<TransportError>> = Err(EVMError::Database(
800            RpcError::Transport(TransportErrorKind::Custom(Box::from("boo".to_string()))),
801        ));
802
803        let result = interpret_evm_result(evm_result, HashMap::new());
804
805        assert!(result.is_err());
806        let err = result.err().unwrap();
807        match err {
808            SimulationEngineError::StorageError(msg) => {
809                assert_eq!(msg, "Storage error: Transport(Custom(\"boo\"))")
810            }
811            _ => panic!("Wrong type of SimulationError!"),
812        }
813    }
814    fn new_state() -> SimulationDB<EVMProvider> {
815        let runtime = get_runtime().expect("Failed to create test runtime");
816        let client = get_client(None).expect("Failed to create test client");
817        SimulationDB::new(client, runtime, None)
818    }
819
820    #[test]
821    fn test_simulate_applies_block_env_overrides() -> Result<(), Box<dyn Error>> {
822        let state = PreCachedDB::new()?;
823        let contract = Address::from_str("0x0000000000000000000000000000000000001234")?;
824        // Minimal runtime bytecode equivalent to the following Solidity contract:
825        //
826        // // SPDX-License-Identifier: UNLICENSED
827        // pragma solidity ^0.8.26;
828        //
829        // contract BlockNumberTest {
830        //     function test() external view returns (uint256) {
831        //         return block.number;
832        //     }
833        // }
834        let bytecode = Bytecode::new_raw(Bytes::from_static(&[
835            0x43, // NUMBER
836            0x60, 0x00, // PUSH1 0
837            0x52, // MSTORE
838            0x60, 0x20, // PUSH1 32
839            0x60, 0x00, // PUSH1 0
840            0xf3, // RETURN
841        ]));
842        let account = AccountInfo::new(U256::ZERO, 0, bytecode.hash_slow(), bytecode);
843        state.init_account(contract, account, None, true)?;
844        state.init_account(Address::ZERO, AccountInfo::default(), None, true)?;
845        state.update(
846            Vec::new(),
847            Some(BlockHeader { number: 1, timestamp: 2, ..Default::default() }),
848        )?;
849
850        let sim_params = SimulationParameters {
851            caller: Address::ZERO,
852            to: contract,
853            data: Vec::new(),
854            value: U256::ZERO,
855            block_overrides: Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) }),
856            ..Default::default()
857        };
858
859        let engine = SimulationEngine::new(state, false);
860        let result = engine
861            .simulate(&sim_params)
862            .expect("simulation should apply block env overrides");
863
864        assert_eq!(U256::from_be_slice(result.result.as_ref()), U256::from(123));
865        Ok(())
866    }
867
868    #[test]
869    fn test_simulate_applies_native_balance_overrides() -> Result<(), Box<dyn Error>> {
870        let state = PreCachedDB::new()?;
871        let contract = Address::from_str("0x0000000000000000000000000000000000001234")?;
872        let bytecode = Bytecode::new_raw(Bytes::from_static(&[
873            0x47, // SELFBALANCE
874            0x60, 0x00, // PUSH1 0
875            0x52, // MSTORE
876            0x60, 0x20, // PUSH1 32
877            0x60, 0x00, // PUSH1 0
878            0xf3, // RETURN
879        ]));
880        let account = AccountInfo::new(U256::ZERO, 0, bytecode.hash_slow(), bytecode);
881        state.init_account(contract, account, None, true)?;
882        state.init_account(Address::ZERO, AccountInfo::default(), None, true)?;
883        state.update(
884            Vec::new(),
885            Some(BlockHeader { number: 1, timestamp: 2, ..Default::default() }),
886        )?;
887        let expected_balance = U256::from(4_200_000_000_000_000_000u64);
888        let sim_params = SimulationParameters {
889            caller: Address::ZERO,
890            to: contract,
891            native_balance_overrides: Some(HashMap::from([(contract, expected_balance)])),
892            ..Default::default()
893        };
894
895        let result = SimulationEngine::new(state, false)
896            .simulate(&sim_params)
897            .expect("simulation should apply the native balance override");
898
899        assert_eq!(U256::from_be_slice(result.result.as_ref()), expected_balance);
900        Ok(())
901    }
902
903    #[test]
904    fn test_integration_revm_v2_swap() -> Result<(), Box<dyn Error>> {
905        let state = new_state();
906
907        // any random address will work
908        let caller = Address::from_str("0x0000000000000000000000000000000000000000")?;
909        let router_addr = Address::from_str("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D")?;
910        let weth_addr = Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")?;
911        let usdc_addr = Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")?;
912
913        // Define the function selector and input arguments
914        let selector = "getAmountsOut(uint256,address[])";
915        let amount_in = U256::from(100_000_000);
916        let path = vec![usdc_addr, weth_addr];
917
918        let encoded = {
919            let args = (amount_in, path);
920            let mut hasher = Keccak256::new();
921            hasher.update(selector.as_bytes());
922            let selector_bytes = &hasher.finalize()[..4];
923            let mut data = selector_bytes.to_vec();
924            let mut encoded_args = args.abi_encode();
925            // Remove extra prefix if present (32 bytes for dynamic data)
926            if encoded_args.len() > 32 &&
927                encoded_args[..32] ==
928                    [0u8; 31]
929                        .into_iter()
930                        .chain([32].to_vec())
931                        .collect::<Vec<u8>>()
932            {
933                encoded_args = encoded_args[32..].to_vec();
934            }
935            data.extend(encoded_args);
936            data
937        };
938
939        // Simulation parameters
940        let sim_params =
941            SimulationParameters { caller, to: router_addr, data: encoded, ..Default::default() };
942        let mut eng = SimulationEngine::new(state, true);
943
944        let block = BlockHeader {
945            number: 23428552,
946            hash: tycho_common::Bytes::from_str(
947                "0x0000000000000000000000000000000000000000000000000000000000000000",
948            )
949            .unwrap(),
950            timestamp: 1758665355,
951            ..Default::default()
952        };
953        eng.state.set_block(Some(block));
954
955        let result = eng.simulate(&sim_params);
956        type BalanceReturn = Vec<U256>;
957        let amounts_out: Vec<U256> = match result {
958            Ok(SimulationResult { result, .. }) => {
959                BalanceReturn::abi_decode(&result).map_err(|e| {
960                    SimulationError::FatalError(format!("Failed to decode result: {e:?}"))
961                })?
962            }
963            _ => panic!("Execution reverted!"),
964        };
965
966        println!(
967            "Swap yielded {} WETH",
968            amounts_out
969                .last()
970                .expect("Empty decoding result")
971        );
972
973        let start = Instant::now();
974        let n_iter = 1000;
975        for _ in 0..n_iter {
976            eng.simulate(&sim_params).unwrap();
977        }
978        let duration = start.elapsed();
979
980        println!("Using revm:");
981        println!("Total Duration [n_iter={n_iter}]: {duration:?}");
982        println!("Single get_amount_out call: {per_call:?}", per_call = duration / n_iter);
983
984        Ok(())
985    }
986
987    #[test]
988    fn test_contract_deployment() -> Result<(), Box<dyn Error>> {
989        let readonly_state = new_state();
990        let state = new_state();
991
992        let selector = "balanceOf(address)";
993        let eoa_address = Address::from_str("0xDFd5293D8e347dFe59E90eFd55b2956a1343963d")?;
994        let calldata = {
995            let args = eoa_address;
996            let mut hasher = Keccak256::new();
997            hasher.update(selector.as_bytes());
998            let selector_bytes = &hasher.finalize()[..4];
999            let mut data = selector_bytes.to_vec();
1000            data.extend(args.abi_encode());
1001            data
1002        };
1003
1004        let usdt_address = Address::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap();
1005        let _ = readonly_state
1006            .basic_ref(usdt_address)
1007            .unwrap()
1008            .unwrap();
1009
1010        // let deploy_bytecode = std::fs::read(
1011        //     "/home/mdank/repos/datarevenue/DEFI/defibot-solver/defibot/swaps/pool_state/dodo/
1012        // compiled/ERC20.bin-runtime" ).unwrap();
1013        // let deploy_bytecode = revm::precompile::Bytes::from(mocked_bytecode);
1014        let _ = Bytes::from(hex::decode("608060405234801562000010575f80fd5b5060405162000a6b38038062000a6b83398101604081905262000033916200012c565b600362000041848262000237565b50600462000050838262000237565b506005805460ff191660ff9290921691909117905550620002ff9050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f83011262000092575f80fd5b81516001600160401b0380821115620000af57620000af6200006e565b604051601f8301601f19908116603f01168101908282118183101715620000da57620000da6200006e565b81604052838152602092508683858801011115620000f6575f80fd5b5f91505b83821015620001195785820183015181830184015290820190620000fa565b5f93810190920192909252949350505050565b5f805f606084860312156200013f575f80fd5b83516001600160401b038082111562000156575f80fd5b620001648783880162000082565b945060208601519150808211156200017a575f80fd5b50620001898682870162000082565b925050604084015160ff81168114620001a0575f80fd5b809150509250925092565b600181811c90821680620001c057607f821691505b602082108103620001df57634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111562000232575f81815260208120601f850160051c810160208610156200020d5750805b601f850160051c820191505b818110156200022e5782815560010162000219565b5050505b505050565b81516001600160401b038111156200025357620002536200006e565b6200026b81620002648454620001ab565b84620001e5565b602080601f831160018114620002a1575f8415620002895750858301515b5f19600386901b1c1916600185901b1785556200022e565b5f85815260208120601f198616915b82811015620002d157888601518255948401946001909101908401620002b0565b5085821015620002ef57878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b61075e806200030d5f395ff3fe608060405234801561000f575f80fd5b50600436106100a6575f3560e01c8063395093511161006e578063395093511461011f57806370a082311461013257806395d89b411461015a578063a457c2d714610162578063a9059cbb14610175578063dd62ed3e14610188575f80fd5b806306fdde03146100aa578063095ea7b3146100c857806318160ddd146100eb57806323b872dd146100fd578063313ce56714610110575b5f80fd5b6100b261019b565b6040516100bf91906105b9565b60405180910390f35b6100db6100d636600461061f565b61022b565b60405190151581526020016100bf565b6002545b6040519081526020016100bf565b6100db61010b366004610647565b610244565b604051601281526020016100bf565b6100db61012d36600461061f565b610267565b6100ef610140366004610680565b6001600160a01b03165f9081526020819052604090205490565b6100b2610288565b6100db61017036600461061f565b610297565b6100db61018336600461061f565b6102f2565b6100ef6101963660046106a0565b6102ff565b6060600380546101aa906106d1565b80601f01602080910402602001604051908101604052809291908181526020018280546101d6906106d1565b80156102215780601f106101f857610100808354040283529160200191610221565b820191905f5260205f20905b81548152906001019060200180831161020457829003601f168201915b5050505050905090565b5f33610238818585610329565b60019150505b92915050565b5f336102518582856103dc565b61025c85858561043e565b506001949350505050565b5f3361023881858561027983836102ff565b6102839190610709565b610329565b6060600480546101aa906106d1565b5f33816102a482866102ff565b9050838110156102e557604051632983c0c360e21b81526001600160a01b038616600482015260248101829052604481018590526064015b60405180910390fd5b61025c8286868403610329565b5f3361023881858561043e565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103525760405163e602df0560e01b81525f60048201526024016102dc565b6001600160a01b03821661037b57604051634a1406b160e11b81525f60048201526024016102dc565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b5f6103e784846102ff565b90505f198114610438578181101561042b57604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064016102dc565b6104388484848403610329565b50505050565b6001600160a01b03831661046757604051634b637e8f60e11b81525f60048201526024016102dc565b6001600160a01b0382166104905760405163ec442f0560e01b81525f60048201526024016102dc565b61049b8383836104a0565b505050565b6001600160a01b0383166104ca578060025f8282546104bf9190610709565b9091555061053a9050565b6001600160a01b0383165f908152602081905260409020548181101561051c5760405163391434e360e21b81526001600160a01b038516600482015260248101829052604481018390526064016102dc565b6001600160a01b0384165f9081526020819052604090209082900390555b6001600160a01b03821661055657600280548290039055610574565b6001600160a01b0382165f9081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516103cf91815260200190565b5f6020808352835180828501525f5b818110156105e4578581018301518582016040015282016105c8565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461061a575f80fd5b919050565b5f8060408385031215610630575f80fd5b61063983610604565b946020939093013593505050565b5f805f60608486031215610659575f80fd5b61066284610604565b925061067060208501610604565b9150604084013590509250925092565b5f60208284031215610690575f80fd5b61069982610604565b9392505050565b5f80604083850312156106b1575f80fd5b6106ba83610604565b91506106c860208401610604565b90509250929050565b600181811c90821680620001c057607f821691505b602082108103620001df57634e487b7160e01b5f52602260045260245ffd5b50919050565b8082018082111561023e57634e487b7160e01b5f52601160045260245ffdfea2646970667358221220dfc123d5852c9246ea16b645b377b4436e2f778438195cc6d6c435e8c73a20e764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000000000")?);
1015
1016        let onchain_bytecode = Bytes::from(hex::decode("608060405234801561000f575f80fd5b50600436106100a6575f3560e01c8063395093511161006e578063395093511461011f57806370a082311461013257806395d89b411461015a578063a457c2d714610162578063a9059cbb14610175578063dd62ed3e14610188575f80fd5b806306fdde03146100aa578063095ea7b3146100c857806318160ddd146100eb57806323b872dd146100fd578063313ce56714610110575b5f80fd5b6100b261019b565b6040516100bf91906105b9565b60405180910390f35b6100db6100d636600461061f565b61022b565b60405190151581526020016100bf565b6002545b6040519081526020016100bf565b6100db61010b366004610647565b610244565b604051601281526020016100bf565b6100db61012d36600461061f565b610267565b6100ef610140366004610680565b6001600160a01b03165f9081526020819052604090205490565b6100b2610288565b6100db61017036600461061f565b610297565b6100db61018336600461061f565b6102f2565b6100ef6101963660046106a0565b6102ff565b6060600380546101aa906106d1565b80601f01602080910402602001604051908101604052809291908181526020018280546101d6906106d1565b80156102215780601f106101f857610100808354040283529160200191610221565b820191905f5260205f20905b81548152906001019060200180831161020457829003601f168201915b5050505050905090565b5f33610238818585610329565b60019150505b92915050565b5f336102518582856103dc565b61025c85858561043e565b506001949350505050565b5f3361023881858561027983836102ff565b6102839190610709565b610329565b6060600480546101aa906106d1565b5f33816102a482866102ff565b9050838110156102e557604051632983c0c360e21b81526001600160a01b038616600482015260248101829052604481018590526064015b60405180910390fd5b61025c8286868403610329565b5f3361023881858561043e565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103525760405163e602df0560e01b81525f60048201526024016102dc565b6001600160a01b03821661037b57604051634a1406b160e11b81525f60048201526024016102dc565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b5f6103e784846102ff565b90505f198114610438578181101561042b57604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064016102dc565b6104388484848403610329565b50505050565b6001600160a01b03831661046757604051634b637e8f60e11b81525f60048201526024016102dc565b6001600160a01b0382166104905760405163ec442f0560e01b81525f60048201526024016102dc565b61049b8383836104a0565b505050565b6001600160a01b0383166104ca578060025f8282546104bf9190610709565b9091555061053a9050565b6001600160a01b0383165f908152602081905260409020548181101561051c5760405163391434e360e21b81526001600160a01b038516600482015260248101829052604481018390526064016102dc565b6001600160a01b0384165f9081526020819052604090209082900390555b6001600160a01b03821661055657600280548290039055610574565b6001600160a01b0382165f9081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516103cf91815260200190565b5f6020808352835180828501525f5b818110156105e4578581018301518582016040015282016105c8565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461061a575f80fd5b919050565b5f8060408385031215610630575f80fd5b61063983610604565b946020939093013593505050565b5f805f60608486031215610659575f80fd5b61066284610604565b925061067060208501610604565b9150604084013590509250925092565b5f60208284031215610690575f80fd5b61069982610604565b9392505050565b5f80604083850312156106b1575f80fd5b6106ba83610604565b91506106c860208401610604565b90509250929050565b600181811c908216806106e557607f821691505b60208210810361070357634e487b7160e01b5f52602260045260245ffd5b50919050565b8082018082111561023e57634e487b7160e01b5f52601160045260245ffdfea2646970667358221220dfc123d5852c9246ea16b645b377b4436e2f778438195cc6d6c435e8c73a20e764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000000000")?);
1017        let code = Bytecode::new_raw(onchain_bytecode);
1018        let contract_acc_info = AccountInfo::new(
1019            U256::from(0),
1020            0,
1021            code.hash_slow(),
1022            code,
1023            // true_usdt.code.unwrap(),
1024        );
1025        // Adding permanent storage for balance
1026        let mut storage = HashMap::default();
1027        storage.insert(
1028            U256::from_str(
1029                "25842306973167774731510882590667189188844731550465818811072464953030320818263",
1030            )
1031            .unwrap(),
1032            U256::from_str("25").unwrap(),
1033        );
1034        // MOCK A BALANCE AND APPROVAL
1035        // let mut permanent_storage = HashMap::new();
1036        // permanent_storage.insert(s)
1037        state
1038            .init_account(usdt_address, contract_acc_info, Some(storage), true)
1039            .expect("Failed to init account");
1040
1041        // DEPLOY A CONTRACT TO GET ON-CHAIN BYTECODE
1042        // let deployment_account = B160::from_str("0x0000000000000000000000000000000000000123")?;
1043        // state.init_account(
1044        //     deployment_account,
1045        //     AccountInfo::new(U256::MAX, 0, Bytecode::default()),
1046        //     None,
1047        //     true,
1048        // );
1049        // let deployment_params = SimulationParameters {
1050        //     caller: Address::from(deployment_account),
1051        //     to: Address::zero(),
1052        //     data: Bytes::from(deploy_bytecode),
1053        //     value: U256::from(0u64),
1054        //     overrides: None,
1055        //     gas_limit: None,
1056        // };
1057
1058        // prepare balanceOf
1059        // let deployed_contract_address =
1060        // B160::from_str("0x5450b634edf901a95af959c99c058086a51836a8")?; Adding overwrite
1061        // for balance
1062        let mut overrides = HashMap::default();
1063        let mut storage_overwrite = HashMap::default();
1064        storage_overwrite.insert(
1065            U256::from_str(
1066                "25842306973167774731510882590667189188844731550465818811072464953030320818263",
1067            )
1068            .unwrap(),
1069            U256::from_str("80").unwrap(),
1070        );
1071        overrides.insert(usdt_address, storage_overwrite);
1072
1073        let sim_params = SimulationParameters {
1074            caller: Address::from_str("0x0000000000000000000000000000000000000000")?,
1075            to: usdt_address,
1076            // to: Address::from(deployed_contract_address),
1077            data: calldata,
1078            overrides: Some(overrides),
1079            ..Default::default()
1080        };
1081
1082        let mut eng = SimulationEngine::new(state, false);
1083
1084        // Dummy block (irrelevant for this test)
1085        let block = BlockHeader {
1086            number: 1,
1087            hash: tycho_common::Bytes::from_str(
1088                "0x0000000000000000000000000000000000000000000000000000000000000000",
1089            )
1090            .unwrap(),
1091            timestamp: 1748397011,
1092            ..Default::default()
1093        };
1094        eng.state.set_block(Some(block));
1095
1096        // println!("Deploying a mocked contract!");
1097        // let deployment_result = eng.simulate(&deployment_params);
1098        // match deployment_result {
1099        //     Ok(SimulationResult { result, state_updates, gas_used }) => {
1100        //         println!("Deployment result: {:?}", result);
1101        //         println!("Used gas: {:?}", gas_used);
1102        //         println!("{:?}", state_updates);
1103        //     }
1104        //     Err(error) => panic!("{:?}", error),
1105        // };
1106
1107        println!("Executing balanceOf");
1108        let result = eng.simulate(&sim_params);
1109        let balance = match result {
1110            Ok(SimulationResult { result, .. }) => U256::abi_decode(&result)?,
1111            Err(error) => panic!("{error:?}"),
1112        };
1113        println!("Balance: {balance}");
1114
1115        Ok(())
1116    }
1117}