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::instruction_table_gas_changes_spec, interpreter::ExtBytecode,
12};
13use revm_primitives::{B256, HashMap, Log, hardfork::SpecId};
14use similar_asserts::assert_eq;
15use std::{fmt, path::Path, sync::OnceLock};
16
17/// Initializes `tracing_subscriber` for tests. Safe to call multiple times.
18pub fn init_tracing() {
19    let _ = tracing_subscriber::fmt::try_init();
20}
21
22/// Default test environment struct for test expected values.
23#[derive(Clone, Debug)]
24pub struct DefEnv {
25    pub tx: DefTx,
26    pub block: DefBlock,
27    pub cfg: DefCfg,
28}
29
30#[derive(Clone, Debug)]
31pub struct DefTx {
32    pub caller: Address,
33    pub blob_hashes: Vec<B256>,
34}
35
36#[derive(Clone, Debug)]
37pub struct DefBlock {
38    pub coinbase: Address,
39    pub timestamp: U256,
40    pub number: U256,
41    pub difficulty: U256,
42    pub prevrandao: Option<B256>,
43    pub gas_limit: U256,
44    pub basefee: U256,
45}
46
47impl DefBlock {
48    pub fn get_blob_gasprice(&self) -> Option<u64> {
49        Some(0) // Default blob gas price for tests
50    }
51}
52
53#[derive(Clone, Debug)]
54pub struct DefCfg {
55    pub chain_id: u64,
56}
57
58impl DefEnv {
59    pub fn effective_gas_price(&self) -> U256 {
60        U256::from(0x4567)
61    }
62}
63
64/// Returns the default test environment.
65pub fn def_env() -> DefEnv {
66    DefEnv {
67        tx: DefTx {
68            caller: Address::repeat_byte(0xcc),
69            blob_hashes: vec![B256::repeat_byte(0x01), B256::repeat_byte(0x02)],
70        },
71        block: DefBlock {
72            coinbase: Address::repeat_byte(0xcb),
73            timestamp: U256::from(0x1234),
74            number: DEF_BN,
75            difficulty: U256::from(0xcdef),
76            prevrandao: Some(B256::from(U256::from(0x0123))),
77            gas_limit: U256::from(0x5678),
78            basefee: U256::from(0x1231),
79        },
80        cfg: DefCfg { chain_id: 69 },
81    }
82}
83
84/// Memory gas calculation: `num_words * 3 + num_words**2 / 512`.
85pub fn memory_gas_cost(num_words: usize) -> u64 {
86    (num_words as u64) * 3 + (num_words as u64) * (num_words as u64) / 512
87}
88
89pub struct TestCase<'a> {
90    pub bytecode: &'a [u8],
91    pub spec_id: SpecId,
92    pub is_static: bool,
93    pub gas_limit: u64,
94
95    /// Override `inspect_stack` on the compiler. `None` uses the default (`true`).
96    pub inspect_stack: Option<bool>,
97    pub modify_ecx: Option<fn(&mut EvmContext<'_>)>,
98
99    pub expected_return: InstructionResult,
100    pub expected_stack: &'a [U256],
101    pub expected_memory: &'a [u8],
102    pub expected_gas: u64,
103    pub expected_next_action: InterpreterAction,
104    pub assert_host: Option<fn(&TestHost)>,
105    pub assert_ecx: Option<fn(&EvmContext<'_>)>,
106}
107
108#[cfg(feature = "__fuzzing")]
109impl<'a> arbitrary::Arbitrary<'a> for TestCase<'a> {
110    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
111        let spec_id_range = 0..=(SpecId::OSAKA as u8 - 1);
112        let spec_id = SpecId::try_from_u8(u.int_in_range(spec_id_range)?).unwrap_or(DEF_SPEC);
113
114        let bytecode: &'a [u8] = u.arbitrary()?;
115
116        Ok(Self::what_interpreter_says(bytecode, spec_id))
117    }
118}
119
120impl Default for TestCase<'_> {
121    fn default() -> Self {
122        Self {
123            bytecode: &[],
124            spec_id: DEF_SPEC,
125            is_static: false,
126            gas_limit: DEF_GAS_LIMIT,
127            inspect_stack: None,
128            modify_ecx: None,
129            expected_return: InstructionResult::Stop,
130            expected_stack: &[],
131            expected_memory: &[],
132            expected_gas: 0,
133            expected_next_action: ACTION_WHAT_INTERPRETER_SAYS,
134            assert_host: None,
135            assert_ecx: None,
136        }
137    }
138}
139
140impl fmt::Debug for TestCase<'_> {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        f.debug_struct("TestCase")
143            .field("bytecode", &format_bytecode(self.bytecode, self.spec_id))
144            .field("spec_id", &self.spec_id)
145            .field("inspect_stack", &self.inspect_stack)
146            .field("modify_ecx", &self.modify_ecx.is_some())
147            .field("expected_return", &self.expected_return)
148            .field("expected_stack", &self.expected_stack)
149            .field("expected_memory", &MemDisplay(self.expected_memory))
150            .field("expected_gas", &self.expected_gas)
151            .field("expected_next_action", &self.expected_next_action)
152            .field("assert_host", &self.assert_host.is_some())
153            .field("assert_ecx", &self.assert_ecx.is_some())
154            .finish()
155    }
156}
157
158impl<'a> TestCase<'a> {
159    pub fn what_interpreter_says(bytecode: &'a [u8], spec_id: SpecId) -> Self {
160        Self {
161            bytecode,
162            spec_id,
163            is_static: false,
164            gas_limit: DEF_GAS_LIMIT,
165            inspect_stack: None,
166            modify_ecx: None,
167            expected_return: RETURN_WHAT_INTERPRETER_SAYS,
168            expected_stack: STACK_WHAT_INTERPRETER_SAYS,
169            expected_memory: MEMORY_WHAT_INTERPRETER_SAYS,
170            expected_gas: GAS_WHAT_INTERPRETER_SAYS,
171            expected_next_action: ACTION_WHAT_INTERPRETER_SAYS,
172            assert_host: None,
173            assert_ecx: None,
174        }
175    }
176}
177
178// Default values.
179pub const DEF_SPEC: SpecId = SpecId::CANCUN;
180pub static DEF_OPINFOS: std::sync::LazyLock<&'static [OpcodeInfo; 256]> =
181    std::sync::LazyLock::new(|| op_info_map(DEF_SPEC));
182
183pub const DEF_GAS_LIMIT: u64 = 100_000;
184pub const DEF_GAS_LIMIT_U256: U256 = U256::from_le_slice(&DEF_GAS_LIMIT.to_le_bytes());
185
186/// Default code address.
187pub const DEF_ADDR: Address = Address::repeat_byte(0xba);
188pub const DEF_CALLER: Address = Address::repeat_byte(0xca);
189pub static DEF_CD: &[u8] = &[0xaa; 64];
190pub static DEF_RD: &[u8] = &[0xbb; 64];
191pub static DEF_DATA: &[u8] = &[0xcc; 64];
192pub const DEF_VALUE: U256 = uint!(123_456_789_U256);
193pub static DEF_STORAGE: OnceLock<HashMap<U256, U256>> = OnceLock::new();
194pub static DEF_CODEMAP: OnceLock<HashMap<Address, revm_bytecode::Bytecode>> = OnceLock::new();
195pub const OTHER_ADDR: Address = Address::repeat_byte(0x69);
196pub const DEF_BN: U256 = uint!(500_U256);
197
198pub const RETURN_WHAT_INTERPRETER_SAYS: InstructionResult = InstructionResult::PrecompileError;
199pub const STACK_WHAT_INTERPRETER_SAYS: &[U256] =
200    &[U256::from_be_slice(&GAS_WHAT_INTERPRETER_SAYS.to_be_bytes())];
201pub const MEMORY_WHAT_INTERPRETER_SAYS: &[u8] = &GAS_WHAT_INTERPRETER_SAYS.to_be_bytes();
202pub const GAS_WHAT_INTERPRETER_SAYS: u64 = 0x4682e332d6612de1;
203pub const ACTION_WHAT_INTERPRETER_SAYS: InterpreterAction =
204    InterpreterAction::Return(InterpreterResult {
205        gas: Gas::new(GAS_WHAT_INTERPRETER_SAYS),
206        output: Bytes::from_static(MEMORY_WHAT_INTERPRETER_SAYS),
207        result: RETURN_WHAT_INTERPRETER_SAYS,
208    });
209
210pub fn def_storage() -> &'static HashMap<U256, U256> {
211    DEF_STORAGE.get_or_init(|| {
212        let mut map = HashMap::default();
213        map.insert(U256::from(0), U256::from(1));
214        map.insert(U256::from(1), U256::from(2));
215        map.insert(U256::from(69), U256::from(42));
216        map
217    })
218}
219
220pub fn def_codemap() -> &'static HashMap<Address, revm_bytecode::Bytecode> {
221    DEF_CODEMAP.get_or_init(|| {
222        let mut map = HashMap::default();
223        map.insert(
224            OTHER_ADDR,
225            revm_bytecode::Bytecode::new_raw(Bytes::from_static(&[
226                op::PUSH1,
227                0x69,
228                op::PUSH1,
229                0x42,
230                op::ADD,
231                op::STOP,
232            ])),
233        );
234        map
235    })
236}
237
238/// Test host that implements [`Host`] trait for testing.
239pub struct TestHost {
240    pub storage: HashMap<U256, U256>,
241    pub transient_storage: HashMap<U256, U256>,
242    pub code_map: &'static HashMap<Address, revm_bytecode::Bytecode>,
243    pub selfdestructs: Vec<(Address, Address)>,
244    pub logs: Vec<Log>,
245    pub gas_params: GasParams,
246}
247
248impl Default for TestHost {
249    fn default() -> Self {
250        Self::new()
251    }
252}
253
254impl TestHost {
255    pub fn new() -> Self {
256        Self::with_spec(DEF_SPEC)
257    }
258
259    pub fn with_spec(spec_id: SpecId) -> Self {
260        Self {
261            storage: def_storage().clone(),
262            transient_storage: HashMap::default(),
263            code_map: def_codemap(),
264            selfdestructs: Vec::new(),
265            logs: Vec::new(),
266            gas_params: GasParams::new_spec(spec_id),
267        }
268    }
269}
270
271impl Host for TestHost {
272    fn basefee(&self) -> U256 {
273        U256::from(0x1231)
274    }
275
276    fn blob_gasprice(&self) -> U256 {
277        U256::ZERO
278    }
279
280    fn gas_limit(&self) -> U256 {
281        U256::from(0x5678)
282    }
283
284    fn gas_params(&self) -> &GasParams {
285        &self.gas_params
286    }
287
288    fn is_amsterdam_eip8037_enabled(&self) -> bool {
289        false
290    }
291
292    fn difficulty(&self) -> U256 {
293        U256::from(0xcdef)
294    }
295
296    fn prevrandao(&self) -> Option<U256> {
297        Some(U256::from(0x0123))
298    }
299
300    fn block_number(&self) -> U256 {
301        DEF_BN
302    }
303
304    fn timestamp(&self) -> U256 {
305        U256::from(0x1234)
306    }
307
308    fn beneficiary(&self) -> Address {
309        Address::repeat_byte(0xcb)
310    }
311
312    fn slot_num(&self) -> U256 {
313        U256::ZERO
314    }
315
316    fn chain_id(&self) -> U256 {
317        U256::from(69)
318    }
319
320    fn effective_gas_price(&self) -> U256 {
321        U256::from(0x4567)
322    }
323
324    fn caller(&self) -> Address {
325        Address::repeat_byte(0xcc)
326    }
327
328    fn blob_hash(&self, number: usize) -> Option<U256> {
329        let env = def_env();
330        env.tx.blob_hashes.get(number).map(|h| (*h).into())
331    }
332
333    fn max_initcode_size(&self) -> usize {
334        // EIP-3860: Max initcode size is 2 * MAX_CODE_SIZE = 2 * 24576 = 49152
335        49152
336    }
337
338    fn block_hash(&mut self, number: u64) -> Option<B256> {
339        Some(U256::from(number).into())
340    }
341
342    fn selfdestruct(
343        &mut self,
344        address: Address,
345        target: Address,
346        _skip_cold_load: bool,
347    ) -> Result<StateLoad<SelfDestructResult>, LoadError> {
348        self.selfdestructs.push((address, target));
349
350        Ok(StateLoad::new(
351            SelfDestructResult {
352                had_value: false,
353                target_exists: true,
354                previously_destroyed: false,
355            },
356            false,
357        ))
358    }
359
360    fn log(&mut self, log: Log) {
361        self.logs.push(log);
362    }
363
364    fn tstore(&mut self, _address: Address, key: U256, value: U256) {
365        self.transient_storage.insert(key, value);
366    }
367
368    fn tload(&mut self, _address: Address, key: U256) -> U256 {
369        self.transient_storage.get(&key).copied().unwrap_or(U256::ZERO)
370    }
371
372    fn load_account_info_skip_cold_load(
373        &mut self,
374        address: Address,
375        load_code: bool,
376        _skip_cold_load: bool,
377    ) -> Result<AccountInfoLoad<'_>, LoadError> {
378        use revm_state::AccountInfo;
379        use std::borrow::Cow;
380
381        let code = if load_code {
382            // Return actual code if found, otherwise empty bytecode
383            Some(self.code_map.get(&address).cloned().unwrap_or_default())
384        } else {
385            None
386        };
387
388        // Return address byte as balance (test convention)
389        // The balance is the last byte of the address
390        let balance = U256::from(address.0[19]);
391
392        // Calculate code hash from the actual bytecode
393        let code_hash = if let Some(bytecode) = self.code_map.get(&address) {
394            keccak256(bytecode.original_byte_slice())
395        } else {
396            KECCAK_EMPTY
397        };
398
399        // Create owned account info
400        let info = AccountInfo { balance, nonce: 0, code_hash, account_id: None, code };
401
402        let is_empty = info.code.is_none() && info.balance.is_zero() && info.nonce == 0;
403
404        Ok(AccountInfoLoad { account: Cow::Owned(info), is_cold: false, is_empty })
405    }
406
407    fn sstore_skip_cold_load(
408        &mut self,
409        _address: Address,
410        key: U256,
411        value: U256,
412        _skip_cold_load: bool,
413    ) -> Result<StateLoad<SStoreResult>, LoadError> {
414        let original = self.storage.get(&key).copied().unwrap_or(U256::ZERO);
415        self.storage.insert(key, value);
416        Ok(StateLoad::new(
417            SStoreResult { original_value: original, present_value: original, new_value: value },
418            false,
419        ))
420    }
421
422    fn sload_skip_cold_load(
423        &mut self,
424        _address: Address,
425        key: U256,
426        _skip_cold_load: bool,
427    ) -> Result<StateLoad<U256>, LoadError> {
428        let value = self.storage.get(&key).copied().unwrap_or(U256::ZERO);
429        Ok(StateLoad::new(value, false))
430    }
431}
432
433pub fn with_evm_context<F: FnOnce(&mut EvmContext<'_>, &mut EvmStack, &mut usize) -> R, R>(
434    bytecode: &[u8],
435    spec_id: SpecId,
436    f: F,
437) -> R {
438    let input = InputsImpl {
439        target_address: DEF_ADDR,
440        bytecode_address: None,
441        caller_address: DEF_CALLER,
442        input: CallInput::Bytes(Bytes::from_static(DEF_CD)),
443        call_value: DEF_VALUE,
444    };
445
446    let bytecode_obj = revm_bytecode::Bytecode::new_raw(Bytes::copy_from_slice(bytecode));
447    let ext_bytecode = ExtBytecode::new(bytecode_obj);
448
449    let mut interpreter =
450        Interpreter::new(SharedMemory::new(), ext_bytecode, input, false, spec_id, DEF_GAS_LIMIT);
451
452    let mut host = TestHost::with_spec(spec_id);
453
454    let (mut ecx, stack, stack_len) =
455        EvmContext::from_interpreter_with_stack(&mut interpreter, &mut host);
456    f(&mut ecx, stack, stack_len)
457}
458
459pub fn set_test_dump<B: Backend>(compiler: &mut EvmCompiler<B>, module_path: &str) {
460    let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
461    let mut dump_path = root.to_path_buf();
462    dump_path.push("target");
463    dump_path.push("tests_dump");
464    // Skip `revmc::tests`.
465    dump_path.extend(module_path.split("::").skip(2));
466    dump_path.push(format!("{:?}", compiler.opt_level()));
467    compiler.set_dump_to(Some(dump_path));
468}
469
470pub fn run_test_case<B: Backend>(test_case: &TestCase<'_>, compiler: &mut EvmCompiler<B>) {
471    let TestCase { bytecode, spec_id, .. } = *test_case;
472    compiler.inspect_stack(test_case.inspect_stack.unwrap_or(true));
473    // compiler.debug_assertions(false);
474    let f = unsafe { compiler.jit("test", bytecode, spec_id) }.unwrap();
475    run_compiled_test_case(test_case, f);
476}
477
478fn run_compiled_test_case(test_case: &TestCase<'_>, f: EvmCompilerFn) {
479    let TestCase {
480        bytecode,
481        spec_id,
482        is_static,
483        gas_limit,
484        inspect_stack: _,
485        modify_ecx,
486        expected_return,
487        expected_stack,
488        expected_memory,
489        expected_gas,
490        ref expected_next_action,
491        assert_host,
492        assert_ecx,
493    } = *test_case;
494
495    with_evm_context(bytecode, spec_id, |ecx, stack, stack_len| {
496        if is_static {
497            ecx.is_static = true;
498        }
499        if gas_limit != DEF_GAS_LIMIT {
500            ecx.gas = Gas::new(gas_limit);
501        }
502        if let Some(modify_ecx) = modify_ecx {
503            modify_ecx(ecx);
504        }
505
506        // Interpreter - run via instruction table
507        let input = InputsImpl {
508            target_address: DEF_ADDR,
509            bytecode_address: None,
510            caller_address: DEF_CALLER,
511            input: CallInput::Bytes(Bytes::from_static(DEF_CD)),
512            call_value: DEF_VALUE,
513        };
514        let bytecode_obj = revm_bytecode::Bytecode::new_raw(Bytes::copy_from_slice(bytecode));
515        let ext_bytecode = ExtBytecode::new(bytecode_obj);
516        let mut interpreter = Interpreter::new(
517            SharedMemory::new(),
518            ext_bytecode,
519            input,
520            is_static,
521            spec_id,
522            gas_limit,
523        );
524
525        let table = instruction_table_gas_changes_spec::<
526            revm_interpreter::interpreter::EthInterpreter,
527            TestHost,
528        >(spec_id);
529        let mut int_host = TestHost::with_spec(spec_id);
530        let interpreter_action = interpreter.run_plain(&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_error();
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_error(),
628                expected_return.is_error(),
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_error() {
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}