Skip to main content

revmc_codegen/tests/
runner.rs

1use super::*;
2use context_interface::{
3    cfg::GasParams,
4    context::{SStoreResult, SelfDestructResult, StateLoad},
5    host::LoadError,
6    journaled_state::AccountInfoLoad,
7};
8use revm_bytecode::opcode as op;
9use revm_interpreter::{
10    CallInput, Host, InputsImpl, Interpreter, SharedMemory,
11    instructions::{gas_table_spec, instruction_table},
12    interpreter::ExtBytecode,
13};
14use revm_primitives::{B256, HashMap, Log, hardfork::SpecId};
15use similar_asserts::assert_eq;
16use std::{fmt, path::Path, sync::OnceLock};
17
18/// Initializes `tracing_subscriber` for tests. Safe to call multiple times.
19pub fn init_tracing() {
20    let _ = tracing_subscriber::fmt::try_init();
21}
22
23/// Default test environment struct for test expected values.
24#[derive(Clone, Debug)]
25pub struct DefEnv {
26    pub tx: DefTx,
27    pub block: DefBlock,
28    pub cfg: DefCfg,
29}
30
31#[derive(Clone, Debug)]
32pub struct DefTx {
33    pub caller: Address,
34    pub blob_hashes: Vec<B256>,
35}
36
37#[derive(Clone, Debug)]
38pub struct DefBlock {
39    pub coinbase: Address,
40    pub timestamp: U256,
41    pub number: U256,
42    pub difficulty: U256,
43    pub prevrandao: Option<B256>,
44    pub gas_limit: U256,
45    pub basefee: U256,
46}
47
48impl DefBlock {
49    pub fn get_blob_gasprice(&self) -> Option<u64> {
50        Some(0) // Default blob gas price for tests
51    }
52}
53
54#[derive(Clone, Debug)]
55pub struct DefCfg {
56    pub chain_id: u64,
57}
58
59impl DefEnv {
60    pub fn effective_gas_price(&self) -> U256 {
61        U256::from(0x4567)
62    }
63}
64
65/// Returns the default test environment.
66pub fn def_env() -> DefEnv {
67    DefEnv {
68        tx: DefTx {
69            caller: Address::repeat_byte(0xcc),
70            blob_hashes: vec![B256::repeat_byte(0x01), B256::repeat_byte(0x02)],
71        },
72        block: DefBlock {
73            coinbase: Address::repeat_byte(0xcb),
74            timestamp: U256::from(0x1234),
75            number: DEF_BN,
76            difficulty: U256::from(0xcdef),
77            prevrandao: Some(B256::from(U256::from(0x0123))),
78            gas_limit: U256::from(0x5678),
79            basefee: U256::from(0x1231),
80        },
81        cfg: DefCfg { chain_id: 69 },
82    }
83}
84
85/// Memory gas calculation: `num_words * 3 + num_words**2 / 512`.
86pub fn memory_gas_cost(num_words: usize) -> u64 {
87    (num_words as u64) * 3 + (num_words as u64) * (num_words as u64) / 512
88}
89
90pub struct TestCase<'a> {
91    pub bytecode: &'a [u8],
92    pub spec_id: SpecId,
93    pub is_static: bool,
94    pub gas_limit: u64,
95
96    /// Override `inspect_stack` on the compiler. `None` uses the default (`true`).
97    pub inspect_stack: Option<bool>,
98    pub modify_ecx: Option<fn(&mut EvmContext<'_>)>,
99
100    pub expected_return: InstructionResult,
101    pub expected_stack: &'a [U256],
102    pub expected_memory: &'a [u8],
103    pub expected_gas: u64,
104    pub expected_next_action: InterpreterAction,
105    pub assert_host: Option<fn(&TestHost)>,
106    pub assert_ecx: Option<fn(&EvmContext<'_>)>,
107}
108
109#[cfg(feature = "__fuzzing")]
110impl<'a> arbitrary::Arbitrary<'a> for TestCase<'a> {
111    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
112        let spec_id_range = 0..=(SpecId::OSAKA as u8 - 1);
113        let spec_id = SpecId::try_from_u8(u.int_in_range(spec_id_range)?).unwrap_or(DEF_SPEC);
114
115        let bytecode: &'a [u8] = u.arbitrary()?;
116
117        Ok(Self::what_interpreter_says(bytecode, spec_id))
118    }
119}
120
121impl Default for TestCase<'_> {
122    fn default() -> Self {
123        Self {
124            bytecode: &[],
125            spec_id: DEF_SPEC,
126            is_static: false,
127            gas_limit: DEF_GAS_LIMIT,
128            inspect_stack: None,
129            modify_ecx: None,
130            expected_return: InstructionResult::Stop,
131            expected_stack: &[],
132            expected_memory: &[],
133            expected_gas: 0,
134            expected_next_action: ACTION_WHAT_INTERPRETER_SAYS,
135            assert_host: None,
136            assert_ecx: None,
137        }
138    }
139}
140
141impl fmt::Debug for TestCase<'_> {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        f.debug_struct("TestCase")
144            .field("bytecode", &format_bytecode(self.bytecode, self.spec_id))
145            .field("spec_id", &self.spec_id)
146            .field("inspect_stack", &self.inspect_stack)
147            .field("modify_ecx", &self.modify_ecx.is_some())
148            .field("expected_return", &self.expected_return)
149            .field("expected_stack", &self.expected_stack)
150            .field("expected_memory", &MemDisplay(self.expected_memory))
151            .field("expected_gas", &self.expected_gas)
152            .field("expected_next_action", &self.expected_next_action)
153            .field("assert_host", &self.assert_host.is_some())
154            .field("assert_ecx", &self.assert_ecx.is_some())
155            .finish()
156    }
157}
158
159impl<'a> TestCase<'a> {
160    pub fn what_interpreter_says(bytecode: &'a [u8], spec_id: SpecId) -> Self {
161        Self {
162            bytecode,
163            spec_id,
164            is_static: false,
165            gas_limit: DEF_GAS_LIMIT,
166            inspect_stack: None,
167            modify_ecx: None,
168            expected_return: RETURN_WHAT_INTERPRETER_SAYS,
169            expected_stack: STACK_WHAT_INTERPRETER_SAYS,
170            expected_memory: MEMORY_WHAT_INTERPRETER_SAYS,
171            expected_gas: GAS_WHAT_INTERPRETER_SAYS,
172            expected_next_action: ACTION_WHAT_INTERPRETER_SAYS,
173            assert_host: None,
174            assert_ecx: None,
175        }
176    }
177}
178
179// Default values.
180pub const DEF_SPEC: SpecId = SpecId::CANCUN;
181pub static DEF_OPINFOS: std::sync::LazyLock<&'static [OpcodeInfo; 256]> =
182    std::sync::LazyLock::new(|| op_info_map(DEF_SPEC));
183
184pub const DEF_GAS_LIMIT: u64 = 100_000;
185pub const DEF_GAS_LIMIT_U256: U256 = U256::from_le_slice(&DEF_GAS_LIMIT.to_le_bytes());
186
187/// Default code address.
188pub const DEF_ADDR: Address = Address::repeat_byte(0xba);
189pub const DEF_CALLER: Address = Address::repeat_byte(0xca);
190pub static DEF_CD: &[u8] = &[0xaa; 64];
191pub static DEF_RD: &[u8] = &[0xbb; 64];
192pub static DEF_DATA: &[u8] = &[0xcc; 64];
193pub const DEF_VALUE: U256 = uint!(123_456_789_U256);
194pub static DEF_STORAGE: OnceLock<HashMap<U256, U256>> = OnceLock::new();
195pub static DEF_CODEMAP: OnceLock<HashMap<Address, revm_bytecode::Bytecode>> = OnceLock::new();
196pub const OTHER_ADDR: Address = Address::repeat_byte(0x69);
197pub const DEF_BN: U256 = uint!(500_U256);
198
199pub const RETURN_WHAT_INTERPRETER_SAYS: InstructionResult = InstructionResult::PrecompileError;
200pub const STACK_WHAT_INTERPRETER_SAYS: &[U256] =
201    &[U256::from_be_slice(&GAS_WHAT_INTERPRETER_SAYS.to_be_bytes())];
202pub const MEMORY_WHAT_INTERPRETER_SAYS: &[u8] = &GAS_WHAT_INTERPRETER_SAYS.to_be_bytes();
203pub const GAS_WHAT_INTERPRETER_SAYS: u64 = 0x4682e332d6612de1;
204pub const ACTION_WHAT_INTERPRETER_SAYS: InterpreterAction =
205    InterpreterAction::Return(InterpreterResult {
206        gas: Gas::new(GAS_WHAT_INTERPRETER_SAYS),
207        output: Bytes::from_static(MEMORY_WHAT_INTERPRETER_SAYS),
208        result: RETURN_WHAT_INTERPRETER_SAYS,
209    });
210
211pub fn def_storage() -> &'static HashMap<U256, U256> {
212    DEF_STORAGE.get_or_init(|| {
213        let mut map = HashMap::default();
214        map.insert(U256::from(0), U256::from(1));
215        map.insert(U256::from(1), U256::from(2));
216        map.insert(U256::from(69), U256::from(42));
217        map
218    })
219}
220
221pub fn def_codemap() -> &'static HashMap<Address, revm_bytecode::Bytecode> {
222    DEF_CODEMAP.get_or_init(|| {
223        let mut map = HashMap::default();
224        map.insert(
225            OTHER_ADDR,
226            revm_bytecode::Bytecode::new_raw(Bytes::from_static(&[
227                op::PUSH1,
228                0x69,
229                op::PUSH1,
230                0x42,
231                op::ADD,
232                op::STOP,
233            ])),
234        );
235        map
236    })
237}
238
239/// Test host that implements [`Host`] trait for testing.
240pub struct TestHost {
241    pub storage: HashMap<U256, U256>,
242    pub transient_storage: HashMap<U256, U256>,
243    pub code_map: &'static HashMap<Address, revm_bytecode::Bytecode>,
244    pub selfdestructs: Vec<(Address, Address)>,
245    pub logs: Vec<Log>,
246    pub gas_params: GasParams,
247}
248
249impl Default for TestHost {
250    fn default() -> Self {
251        Self::new()
252    }
253}
254
255impl TestHost {
256    pub fn new() -> Self {
257        Self::with_spec(DEF_SPEC)
258    }
259
260    pub fn with_spec(spec_id: SpecId) -> Self {
261        Self {
262            storage: def_storage().clone(),
263            transient_storage: HashMap::default(),
264            code_map: def_codemap(),
265            selfdestructs: Vec::new(),
266            logs: Vec::new(),
267            gas_params: GasParams::new_spec(spec_id),
268        }
269    }
270}
271
272impl Host for TestHost {
273    fn basefee(&self) -> U256 {
274        U256::from(0x1231)
275    }
276
277    fn blob_gasprice(&self) -> U256 {
278        U256::ZERO
279    }
280
281    fn gas_limit(&self) -> U256 {
282        U256::from(0x5678)
283    }
284
285    fn gas_params(&self) -> &GasParams {
286        &self.gas_params
287    }
288
289    fn is_amsterdam_eip8037_enabled(&self) -> bool {
290        false
291    }
292
293    fn difficulty(&self) -> U256 {
294        U256::from(0xcdef)
295    }
296
297    fn prevrandao(&self) -> Option<U256> {
298        Some(U256::from(0x0123))
299    }
300
301    fn block_number(&self) -> U256 {
302        DEF_BN
303    }
304
305    fn timestamp(&self) -> U256 {
306        U256::from(0x1234)
307    }
308
309    fn beneficiary(&self) -> Address {
310        Address::repeat_byte(0xcb)
311    }
312
313    fn slot_num(&self) -> U256 {
314        U256::ZERO
315    }
316
317    fn chain_id(&self) -> U256 {
318        U256::from(69)
319    }
320
321    fn effective_gas_price(&self) -> U256 {
322        U256::from(0x4567)
323    }
324
325    fn caller(&self) -> Address {
326        Address::repeat_byte(0xcc)
327    }
328
329    fn blob_hash(&self, number: usize) -> Option<U256> {
330        let env = def_env();
331        env.tx.blob_hashes.get(number).map(|h| (*h).into())
332    }
333
334    fn max_initcode_size(&self) -> usize {
335        // EIP-3860: Max initcode size is 2 * MAX_CODE_SIZE = 2 * 24576 = 49152
336        49152
337    }
338
339    fn block_hash(&mut self, number: u64) -> Option<B256> {
340        Some(U256::from(number).into())
341    }
342
343    fn selfdestruct(
344        &mut self,
345        address: Address,
346        target: Address,
347        _skip_cold_load: bool,
348    ) -> Result<StateLoad<SelfDestructResult>, LoadError> {
349        self.selfdestructs.push((address, target));
350
351        Ok(StateLoad::new(
352            SelfDestructResult {
353                had_value: false,
354                target_exists: true,
355                previously_destroyed: false,
356            },
357            false,
358        ))
359    }
360
361    fn log(&mut self, log: Log) {
362        self.logs.push(log);
363    }
364
365    fn tstore(&mut self, _address: Address, key: U256, value: U256) {
366        self.transient_storage.insert(key, value);
367    }
368
369    fn tload(&mut self, _address: Address, key: U256) -> U256 {
370        self.transient_storage.get(&key).copied().unwrap_or(U256::ZERO)
371    }
372
373    fn load_account_info_skip_cold_load(
374        &mut self,
375        address: Address,
376        load_code: bool,
377        _skip_cold_load: bool,
378    ) -> Result<AccountInfoLoad<'_>, LoadError> {
379        use revm_state::AccountInfo;
380        use std::borrow::Cow;
381
382        let code = if load_code {
383            // Return actual code if found, otherwise empty bytecode
384            Some(self.code_map.get(&address).cloned().unwrap_or_default())
385        } else {
386            None
387        };
388
389        // Return address byte as balance (test convention)
390        // The balance is the last byte of the address
391        let balance = U256::from(address.0[19]);
392
393        // Calculate code hash from the actual bytecode
394        let code_hash = if let Some(bytecode) = self.code_map.get(&address) {
395            keccak256(bytecode.original_byte_slice())
396        } else {
397            KECCAK_EMPTY
398        };
399
400        // Create owned account info
401        let info = AccountInfo { balance, nonce: 0, code_hash, account_id: None, code };
402
403        let is_empty = info.code.is_none() && info.balance.is_zero() && info.nonce == 0;
404
405        Ok(AccountInfoLoad { account: Cow::Owned(info), is_cold: false, is_empty })
406    }
407
408    fn sstore_skip_cold_load(
409        &mut self,
410        _address: Address,
411        key: U256,
412        value: U256,
413        _skip_cold_load: bool,
414    ) -> Result<StateLoad<SStoreResult>, LoadError> {
415        let original = self.storage.get(&key).copied().unwrap_or(U256::ZERO);
416        self.storage.insert(key, value);
417        Ok(StateLoad::new(
418            SStoreResult { original_value: original, present_value: original, new_value: value },
419            false,
420        ))
421    }
422
423    fn sload_skip_cold_load(
424        &mut self,
425        _address: Address,
426        key: U256,
427        _skip_cold_load: bool,
428    ) -> Result<StateLoad<U256>, LoadError> {
429        let value = self.storage.get(&key).copied().unwrap_or(U256::ZERO);
430        Ok(StateLoad::new(value, false))
431    }
432}
433
434pub fn with_evm_context<F: FnOnce(&mut EvmContext<'_>, &mut EvmStack, &mut usize) -> R, R>(
435    bytecode: &[u8],
436    spec_id: SpecId,
437    f: F,
438) -> R {
439    let input = InputsImpl {
440        target_address: DEF_ADDR,
441        bytecode_address: None,
442        caller_address: DEF_CALLER,
443        input: CallInput::Bytes(Bytes::from_static(DEF_CD)),
444        call_value: DEF_VALUE,
445    };
446
447    let bytecode_obj = revm_bytecode::Bytecode::new_raw(Bytes::copy_from_slice(bytecode));
448    let ext_bytecode = ExtBytecode::new(bytecode_obj);
449
450    let mut interpreter =
451        Interpreter::new(SharedMemory::new(), ext_bytecode, input, false, spec_id, DEF_GAS_LIMIT);
452
453    let mut host = TestHost::with_spec(spec_id);
454
455    let (mut ecx, stack, stack_len) =
456        EvmContext::from_interpreter_with_stack(&mut interpreter, &mut host);
457    f(&mut ecx, stack, stack_len)
458}
459
460pub fn set_test_dump<B: Backend>(compiler: &mut EvmCompiler<B>, module_path: &str) {
461    let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
462    let mut dump_path = root.to_path_buf();
463    dump_path.push("target");
464    dump_path.push("tests_dump");
465    // Skip `revmc::tests`.
466    dump_path.extend(module_path.split("::").skip(2));
467    dump_path.push(format!("{:?}", compiler.opt_level()));
468    compiler.set_dump_to(Some(dump_path));
469}
470
471pub fn run_test_case<B: Backend>(test_case: &TestCase<'_>, compiler: &mut EvmCompiler<B>) {
472    let TestCase { bytecode, spec_id, .. } = *test_case;
473    compiler.inspect_stack(test_case.inspect_stack.unwrap_or(true));
474    // compiler.debug_assertions(false);
475    let f = unsafe { compiler.jit("test", bytecode, spec_id) }.unwrap();
476    run_compiled_test_case(test_case, f);
477}
478
479fn run_compiled_test_case(test_case: &TestCase<'_>, f: EvmCompilerFn) {
480    let TestCase {
481        bytecode,
482        spec_id,
483        is_static,
484        gas_limit,
485        inspect_stack: _,
486        modify_ecx,
487        expected_return,
488        expected_stack,
489        expected_memory,
490        expected_gas,
491        ref expected_next_action,
492        assert_host,
493        assert_ecx,
494    } = *test_case;
495
496    with_evm_context(bytecode, spec_id, |ecx, stack, stack_len| {
497        if is_static {
498            ecx.is_static = true;
499        }
500        if gas_limit != DEF_GAS_LIMIT {
501            ecx.gas = Gas::new(gas_limit);
502        }
503        if let Some(modify_ecx) = modify_ecx {
504            modify_ecx(ecx);
505            ecx.refresh_memory_cache();
506        }
507
508        // Interpreter - run via instruction table
509        let input = InputsImpl {
510            target_address: DEF_ADDR,
511            bytecode_address: None,
512            caller_address: DEF_CALLER,
513            input: CallInput::Bytes(Bytes::from_static(DEF_CD)),
514            call_value: DEF_VALUE,
515        };
516        let bytecode_obj = revm_bytecode::Bytecode::new_raw(Bytes::copy_from_slice(bytecode));
517        let ext_bytecode = ExtBytecode::new(bytecode_obj);
518        let mut interpreter = Interpreter::new(
519            SharedMemory::new(),
520            ext_bytecode,
521            input,
522            is_static,
523            spec_id,
524            gas_limit,
525        );
526
527        let table = instruction_table::<revm_interpreter::interpreter::EthInterpreter, TestHost>();
528        let gas_table = gas_table_spec(spec_id);
529        let mut int_host = TestHost::with_spec(spec_id);
530        let interpreter_action = interpreter.run_plain(&table, &gas_table, &mut int_host);
531
532        let int_result = match &interpreter_action {
533            InterpreterAction::Return(result) => result.result,
534            _ => InstructionResult::Stop,
535        };
536
537        let mut expected_return = expected_return;
538        if expected_return == RETURN_WHAT_INTERPRETER_SAYS {
539            expected_return = int_result;
540        } else if modify_ecx.is_none() {
541            // Only check interpreter return if modify_ecx is not set.
542            // When modify_ecx is used, it only modifies the JIT context, not the interpreter's
543            // input, so the interpreter may return a different result.
544            assert_eq!(int_result, expected_return, "interpreter return value mismatch");
545        }
546
547        // When modify_ecx is set, the interpreter runs with different inputs than the JIT,
548        // so we cannot use interpreter results as expected values or compare against them.
549        let skip_interpreter_checks = modify_ecx.is_some() || expected_return.is_halt();
550
551        let mut expected_stack = expected_stack;
552        if expected_stack == STACK_WHAT_INTERPRETER_SAYS {
553            if skip_interpreter_checks {
554                expected_stack = &[]; // Will skip comparison below
555            } else {
556                expected_stack = interpreter.stack.data();
557            }
558        } else if !skip_interpreter_checks {
559            assert_eq!(interpreter.stack.data(), expected_stack, "interpreter stack mismatch");
560        }
561
562        let interpreter_memory = interpreter.memory.context_memory();
563        let mut expected_memory = expected_memory;
564        if expected_memory == MEMORY_WHAT_INTERPRETER_SAYS {
565            if skip_interpreter_checks {
566                expected_memory = &[]; // Will skip comparison below
567            } else {
568                expected_memory = &*interpreter_memory;
569            }
570        } else if !skip_interpreter_checks {
571            assert_eq!(
572                MemDisplay(&interpreter_memory),
573                MemDisplay(expected_memory),
574                "interpreter memory mismatch"
575            );
576        }
577
578        let mut expected_gas = expected_gas;
579        if expected_gas == GAS_WHAT_INTERPRETER_SAYS {
580            if skip_interpreter_checks {
581                expected_gas = 0; // Will skip comparison below
582            } else {
583                expected_gas = interpreter.gas.total_gas_spent();
584            }
585        } else if !skip_interpreter_checks {
586            assert_eq!(interpreter.gas.total_gas_spent(), expected_gas, "interpreter gas mismatch");
587        }
588
589        // Track whether we should skip JIT stack/gas/memory comparisons
590        let skip_jit_stack =
591            skip_interpreter_checks && test_case.expected_stack == STACK_WHAT_INTERPRETER_SAYS;
592        let skip_jit_memory =
593            skip_interpreter_checks && test_case.expected_memory == MEMORY_WHAT_INTERPRETER_SAYS;
594        let skip_jit_gas =
595            skip_interpreter_checks && test_case.expected_gas == GAS_WHAT_INTERPRETER_SAYS;
596
597        // This is what the interpreter returns when the internal action is None in `run`.
598        let default_action = InterpreterAction::Return(InterpreterResult {
599            result: int_result,
600            output: Bytes::new(),
601            gas: interpreter.gas,
602        });
603        let mut expected_next_action = expected_next_action;
604        if *expected_next_action == ACTION_WHAT_INTERPRETER_SAYS {
605            expected_next_action = &interpreter_action;
606        } else if modify_ecx.is_none() {
607            // Only check interpreter action if modify_ecx is not set.
608            // When modify_ecx is used, it only modifies the JIT context, not the interpreter's
609            // input, so the interpreter may return a different action.
610            assert_actions(&interpreter_action, expected_next_action);
611        }
612
613        if let Some(assert_host) = assert_host {
614            assert_host(&int_host);
615        }
616
617        let actual_return = unsafe { f.call(stack, stack_len, ecx) };
618
619        if matches!(
620            actual_return,
621            // We can have a stack overflow/underflow before other error codes due to sections.
622            |InstructionResult::StackOverflow| InstructionResult::StackUnderflow
623            // Any OOG is equivalent. We skip `InvalidOperand` sometimes.
624            | InstructionResult::OutOfGas | InstructionResult::MemoryOOG | InstructionResult::InvalidOperandOOG
625        ) {
626            assert_eq!(
627                actual_return.is_halt(),
628                expected_return.is_halt(),
629                "return value mismatch: {actual_return:?} != {expected_return:?}"
630            );
631        } else {
632            assert_eq!(actual_return, expected_return, "return value mismatch");
633        }
634
635        let actual_stack =
636            unsafe { stack.as_slice(*stack_len).iter().map(|x| x.to_u256()).collect::<Vec<_>>() };
637
638        // On EVM halt all available gas is consumed, so resulting stack, memory, and gas do not
639        // matter. We do less work than the interpreter by bailing out earlier due to sections.
640        if !actual_return.is_halt() {
641            if !skip_jit_stack {
642                assert_eq!(actual_stack, *expected_stack, "stack mismatch");
643            }
644
645            if !skip_jit_memory {
646                assert_eq!(
647                    MemDisplay(&ecx.memory.context_memory()),
648                    MemDisplay(expected_memory),
649                    "memory mismatch"
650                );
651            }
652
653            if !skip_jit_gas {
654                assert_eq!(ecx.gas.total_gas_spent(), expected_gas, "gas mismatch");
655            }
656        }
657
658        let actual_next_action = match ecx.next_action.as_ref() {
659            Some(action) => action,
660            None => &default_action,
661        };
662        assert_actions(actual_next_action, expected_next_action);
663
664        if let Some(_assert_host) = assert_host {
665            let host = unsafe { &*(ecx.host as *mut dyn Host as *mut TestHost) };
666            _assert_host(host);
667        }
668
669        if let Some(assert_ecx) = assert_ecx {
670            assert_ecx(ecx);
671        }
672    });
673}
674
675#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
676struct MemDisplay<'a>(&'a [u8]);
677impl fmt::Debug for MemDisplay<'_> {
678    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
679        let chunks = self.0.chunks(32).map(revm_primitives::hex::encode_prefixed);
680        f.debug_list().entries(chunks).finish()
681    }
682}
683
684#[track_caller]
685fn assert_actions(actual: &InterpreterAction, expected: &InterpreterAction) {
686    match (actual, expected) {
687        (InterpreterAction::Return(result), InterpreterAction::Return(expected_result)) => {
688            assert_eq!(result.result, expected_result.result, "result mismatch");
689            assert_eq!(result.output, expected_result.output, "result output mismatch");
690            if expected_result.gas.limit() != GAS_WHAT_INTERPRETER_SAYS {
691                assert_eq!(
692                    result.gas.total_gas_spent(),
693                    expected_result.gas.total_gas_spent(),
694                    "result gas mismatch"
695                );
696            }
697        }
698        (
699            InterpreterAction::NewFrame(FrameInput::Call(actual_call)),
700            InterpreterAction::NewFrame(FrameInput::Call(expected_call)),
701        ) => {
702            // Compare CallInputs fields, allowing for differences in input representation
703            // and known_bytecode (JIT doesn't preload, interpreter may)
704            assert_eq!(
705                actual_call.return_memory_offset, expected_call.return_memory_offset,
706                "return_memory_offset mismatch"
707            );
708            assert_eq!(actual_call.gas_limit, expected_call.gas_limit, "gas_limit mismatch");
709            assert_eq!(
710                actual_call.bytecode_address, expected_call.bytecode_address,
711                "bytecode_address mismatch"
712            );
713            assert_eq!(
714                actual_call.target_address, expected_call.target_address,
715                "target_address mismatch"
716            );
717            assert_eq!(actual_call.caller, expected_call.caller, "caller mismatch");
718            assert_eq!(actual_call.value, expected_call.value, "value mismatch");
719            assert_eq!(actual_call.scheme, expected_call.scheme, "scheme mismatch");
720            assert_eq!(actual_call.is_static, expected_call.is_static, "is_static mismatch");
721            // Note: We don't compare `input` directly as it may use different shared-memory
722            // ranges.
723            // Note: We don't compare `known_bytecode` as JIT doesn't preload.
724        }
725        (
726            InterpreterAction::NewFrame(FrameInput::Create(actual_create)),
727            InterpreterAction::NewFrame(FrameInput::Create(expected_create)),
728        ) => {
729            // Compare CreateInputs fields
730            assert_eq!(actual_create.caller(), expected_create.caller(), "caller mismatch");
731            assert_eq!(actual_create.scheme(), expected_create.scheme(), "scheme mismatch");
732            assert_eq!(actual_create.value(), expected_create.value(), "value mismatch");
733            assert_eq!(
734                actual_create.init_code(),
735                expected_create.init_code(),
736                "init_code mismatch"
737            );
738            assert_eq!(
739                actual_create.gas_limit(),
740                expected_create.gas_limit(),
741                "gas_limit mismatch"
742            );
743        }
744        (a, b) => assert_eq!(a, b, "next action mismatch"),
745    }
746}
747
748/// Insert a call outcome into the interpreter state (for testing call_with_interpreter).
749///
750/// Mimics revm-handler's insert_call_outcome: pushes success indicator, copies return data
751/// to memory, and returns unspent gas.
752pub fn insert_call_outcome_test(
753    interpreter: &mut revm_interpreter::Interpreter,
754    outcome: InterpreterResult,
755    return_memory_offset: Option<std::ops::Range<usize>>,
756) {
757    use revm_interpreter::interpreter_types::ReturnData;
758
759    let ins_result = outcome.result;
760    let out_gas = outcome.gas;
761    let returned_len = outcome.output.len();
762
763    interpreter.return_data.set_buffer(outcome.output);
764
765    let success_indicator = if ins_result.is_ok() { U256::from(1) } else { U256::ZERO };
766    let _ = interpreter.stack.push(success_indicator);
767
768    if ins_result.is_ok_or_revert() {
769        interpreter.gas.erase_cost(out_gas.remaining());
770
771        if let Some(mem_range) = return_memory_offset {
772            let target_len = std::cmp::min(mem_range.len(), returned_len);
773            if target_len > 0 {
774                interpreter
775                    .memory
776                    .set(mem_range.start, &interpreter.return_data.buffer()[..target_len]);
777            }
778        }
779    }
780
781    if ins_result.is_ok() {
782        interpreter.gas.record_refund(out_gas.refunded());
783    }
784}