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