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