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#[derive(Debug, Display, Clone, PartialEq)]
31pub enum SimulationEngineError {
32 StorageError(String),
35 OutOfGas(String, String),
38 TransactionError { data: String, gas_used: Option<u64> },
40 TraceError(String),
42}
43
44#[derive(Debug, Clone, Default)]
46pub struct SimulationResult {
47 pub result: Bytes,
49 pub state_updates: HashMap<Address, StateUpdate>,
51 pub gas_used: u64,
53 pub transient_storage: HashMap<Address, HashMap<U256, U256>>,
55}
56
57#[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 pub fn new(state: D, trace: bool) -> Self {
84 Self { state, trace }
85 }
86
87 pub fn simulate(
91 &self,
92 params: &SimulationParameters,
93 ) -> Result<SimulationResult, SimulationEngineError> {
94 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(¶ms.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) = ¶ms.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) = ¶ms.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 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 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 (
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 handle.block_on(future)
258 } else {
259 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
273fn 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
336fn 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 let mut account_updates: HashMap<Address, StateUpdate> = HashMap::new();
356 for (address, account) in state {
357 account_updates.insert(
358 address,
359 StateUpdate {
360 balance: Some(account.info.balance),
362 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)]
391pub struct SimulationParameters {
393 pub caller: Address,
395 pub to: Address,
397 pub data: Vec<u8>,
399 pub value: U256,
401 pub overrides: Option<HashMap<Address, HashMap<U256, U256>>>,
404 pub gas_limit: Option<u64>,
406 pub transient_storage: Option<HashMap<Address, HashMap<U256, U256>>>,
409 pub block_overrides: Option<BlockEnvOverrides>,
411 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#[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 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 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 #[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 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 (
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 (
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 let bytecode = Bytecode::new_raw(Bytes::from_static(&[
835 0x43, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ]));
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, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ]));
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 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 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 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 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 _ = 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 );
1025 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 state
1038 .init_account(usdt_address, contract_acc_info, Some(storage), true)
1039 .expect("Failed to init account");
1040
1041 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 data: calldata,
1078 overrides: Some(overrides),
1079 ..Default::default()
1080 };
1081
1082 let mut eng = SimulationEngine::new(state, false);
1083
1084 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!("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}