revmc_state_tests/
lib.rs

1//! Ethereum state tests for revmc JIT compiler.
2//!
3//! Runs tests from ethereum/tests against the JIT compiler and compares
4//! results with the revm interpreter.
5
6use eyre::{eyre, Result};
7use revm::{
8    bytecode::Bytecode,
9    context::{BlockEnv, CfgEnv, Context, Journal, TxEnv},
10    context_interface::JournalTr,
11    database::CacheDB,
12    database_interface::EmptyDB,
13    interpreter::{
14        instructions::instruction_table_gas_changes_spec,
15        interpreter::{EthInterpreter, ExtBytecode},
16        interpreter_types::ReturnData,
17        FrameInput, InputsImpl, Interpreter, InterpreterAction, InterpreterResult, SharedMemory,
18    },
19    primitives::{hardfork::SpecId, keccak256, Bytes, TxKind, B256, U256},
20    state::AccountInfo,
21};
22use revm_statetest_types::{SpecName, TestSuite, TestUnit};
23use revmc::{
24    llvm::inkwell::context::Context as LlvmContext, Backend, EvmCompiler, EvmCompilerFn,
25    EvmContext, EvmLlvmBackend, EvmStack, OptimizationLevel,
26};
27use std::{
28    cmp::min,
29    collections::HashMap,
30    env,
31    ops::Range,
32    path::{Path, PathBuf},
33};
34use walkdir::WalkDir;
35
36/// Default path to ethereum/tests repository
37const DEFAULT_ETHTESTS_PATH: &str = "tests/ethereum-tests";
38
39/// Get the path to ethereum/tests
40pub fn get_ethtests_path() -> PathBuf {
41    env::var("ETHTESTS").map(PathBuf::from).unwrap_or_else(|_| PathBuf::from(DEFAULT_ETHTESTS_PATH))
42}
43
44/// Find all JSON test files in a directory
45pub fn find_json_tests(path: &Path) -> Vec<PathBuf> {
46    WalkDir::new(path)
47        .into_iter()
48        .filter_map(|e| e.ok())
49        .filter(|e| e.path().extension().is_some_and(|ext| ext == "json"))
50        .map(|e| e.path().to_path_buf())
51        .collect()
52}
53
54/// Load a test suite from a JSON file
55pub fn load_test_suite(path: &Path) -> Result<TestSuite> {
56    let content = std::fs::read_to_string(path)?;
57    let suite: TestSuite = serde_json::from_str(&content)?;
58    Ok(suite)
59}
60
61/// Convert SpecName to SpecId
62pub fn spec_name_to_spec_id(spec_name: &SpecName) -> Option<SpecId> {
63    match *spec_name {
64        SpecName::Frontier => Some(SpecId::FRONTIER),
65        SpecName::Homestead => Some(SpecId::HOMESTEAD),
66        SpecName::EIP150 => Some(SpecId::TANGERINE),
67        SpecName::EIP158 => Some(SpecId::SPURIOUS_DRAGON),
68        SpecName::Byzantium => Some(SpecId::BYZANTIUM),
69        SpecName::Constantinople => None, // Skip, has reentrancy bug
70        SpecName::ConstantinopleFix => Some(SpecId::PETERSBURG),
71        SpecName::Istanbul => Some(SpecId::ISTANBUL),
72        SpecName::Berlin => Some(SpecId::BERLIN),
73        SpecName::London => Some(SpecId::LONDON),
74        SpecName::Paris | SpecName::Merge => Some(SpecId::MERGE),
75        SpecName::Shanghai => Some(SpecId::SHANGHAI),
76        SpecName::Cancun => Some(SpecId::CANCUN),
77        SpecName::Prague => Some(SpecId::PRAGUE),
78        SpecName::Osaka => Some(SpecId::OSAKA),
79        _ => None, // Skip transition specs and unknown
80    }
81}
82
83/// Test result from running a single test case
84#[derive(Debug)]
85pub struct TestResult {
86    pub name: String,
87    pub spec: String,
88    pub passed: bool,
89    pub error: Option<String>,
90}
91
92/// JIT-compiled bytecode cache
93pub struct CompiledContracts {
94    functions: HashMap<B256, EvmCompilerFn>,
95}
96
97impl CompiledContracts {
98    pub fn new() -> Self {
99        Self { functions: HashMap::new() }
100    }
101
102    pub fn get(&self, code_hash: &B256) -> Option<EvmCompilerFn> {
103        self.functions.get(code_hash).copied()
104    }
105
106    pub fn insert(&mut self, code_hash: B256, func: EvmCompilerFn) {
107        self.functions.insert(code_hash, func);
108    }
109}
110
111impl Default for CompiledContracts {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117/// Build pre-state database from TestUnit
118pub fn build_pre_state(unit: &TestUnit) -> CacheDB<EmptyDB> {
119    let mut db = CacheDB::new(EmptyDB::new());
120
121    for (address, info) in &unit.pre {
122        let code_hash = keccak256(&info.code);
123        let bytecode =
124            if info.code.is_empty() { None } else { Some(Bytecode::new_legacy(info.code.clone())) };
125
126        let acc_info = AccountInfo {
127            balance: info.balance,
128            nonce: info.nonce,
129            code_hash,
130            code: bytecode,
131            ..Default::default()
132        };
133
134        db.insert_account_info(*address, acc_info);
135
136        for (key, value) in &info.storage {
137            let _ = db.insert_account_storage(*address, *key, *value);
138        }
139    }
140
141    db
142}
143
144/// Build transaction environment from test indices
145pub fn build_tx_env(unit: &TestUnit, test: &revm_statetest_types::Test) -> Result<TxEnv> {
146    test.tx_env(unit).map_err(|e| eyre!("Failed to build tx env: {:?}", e))
147}
148
149/// Compile all contracts in pre-state using JIT
150///
151/// This uses a two-phase approach:
152/// 1. Translate all contracts (before module finalization)
153/// 2. JIT all translated functions (first call finalizes the module)
154///
155/// This is necessary because `translate()` cannot be called after finalization,
156/// but `jit_function()` can be called multiple times after the module is finalized.
157pub fn compile_contracts<'ctx>(
158    unit: &TestUnit,
159    spec_id: SpecId,
160    compiler: &mut EvmCompiler<EvmLlvmBackend<'ctx>>,
161) -> Result<CompiledContracts> {
162    let mut compiled = CompiledContracts::new();
163    let mut func_ids: Vec<(B256, <EvmLlvmBackend<'ctx> as Backend>::FuncId)> = Vec::new();
164
165    // Phase 1: Translate all contracts (before finalization)
166    for (address, info) in &unit.pre {
167        if info.code.is_empty() {
168            continue;
169        }
170
171        let code_hash = keccak256(&info.code);
172        // Skip if we already have this code hash queued
173        if func_ids.iter().any(|(hash, _)| hash == &code_hash) {
174            continue;
175        }
176
177        let name = format!("contract_{:x}", address);
178        let func_id = compiler
179            .translate(&name, &info.code[..], spec_id)
180            .map_err(|e| eyre!("Failed to translate contract {:x}: {}", address, e))?;
181
182        func_ids.push((code_hash, func_id));
183    }
184
185    // Phase 2: JIT all translated functions (first call finalizes the module)
186    for (code_hash, func_id) in func_ids {
187        let func = unsafe { compiler.jit_function(func_id) }
188            .map_err(|e| eyre!("Failed to JIT function for {:x}: {}", code_hash, e))?;
189
190        compiled.insert(code_hash, func);
191    }
192
193    Ok(compiled)
194}
195
196/// Run a single test unit with the JIT compiler
197pub fn run_test_unit(name: &str, unit: &TestUnit, spec_name: &SpecName) -> Result<Vec<TestResult>> {
198    let spec_str = format!("{:?}", spec_name);
199
200    let Some(spec_id) = spec_name_to_spec_id(spec_name) else {
201        return Ok(vec![TestResult {
202            name: name.to_string(),
203            spec: spec_str,
204            passed: true,
205            error: Some("Skipped: unsupported spec".to_string()),
206        }]);
207    };
208
209    let tests = match unit.post.get(spec_name) {
210        Some(tests) => tests,
211        None => return Ok(vec![]),
212    };
213
214    let mut results = Vec::new();
215
216    for (idx, test) in tests.iter().enumerate() {
217        let result = run_single_test(name, unit, test, spec_id, idx);
218        results.push(TestResult {
219            name: format!("{}[{}]", name, idx),
220            spec: spec_str.clone(),
221            passed: result.is_ok(),
222            error: result.err().map(|e| e.to_string()),
223        });
224    }
225
226    Ok(results)
227}
228
229/// Run a single test case comparing JIT vs interpreter
230fn run_single_test(
231    name: &str,
232    unit: &TestUnit,
233    test: &revm_statetest_types::Test,
234    spec_id: SpecId,
235    idx: usize,
236) -> Result<()> {
237    let has_code = unit.pre.values().any(|acc| !acc.code.is_empty());
238    if !has_code {
239        return Ok(());
240    }
241
242    if test.indexes.data >= unit.transaction.data.len() {
243        return Err(eyre!(
244            "Test {}[{}]: invalid data index {} >= {}",
245            name,
246            idx,
247            test.indexes.data,
248            unit.transaction.data.len()
249        ));
250    }
251    if test.indexes.gas >= unit.transaction.gas_limit.len() {
252        return Err(eyre!(
253            "Test {}[{}]: invalid gas index {} >= {}",
254            name,
255            idx,
256            test.indexes.gas,
257            unit.transaction.gas_limit.len()
258        ));
259    }
260    if test.indexes.value >= unit.transaction.value.len() {
261        return Err(eyre!(
262            "Test {}[{}]: invalid value index {} >= {}",
263            name,
264            idx,
265            test.indexes.value,
266            unit.transaction.value.len()
267        ));
268    }
269
270    let tx_env = build_tx_env(unit, test)?;
271    let db = build_pre_state(unit);
272
273    let interpreter_result = run_with_interpreter(unit, &tx_env, db.clone(), spec_id)?;
274
275    let context = LlvmContext::create();
276    let backend = EvmLlvmBackend::new(&context, false, OptimizationLevel::Default)?;
277    let mut compiler = EvmCompiler::new(backend);
278    let compiled = compile_contracts(unit, spec_id, &mut compiler)?;
279    let jit_result = run_with_jit(unit, &tx_env, db, spec_id, &compiled)?;
280
281    if interpreter_result.success != jit_result.success {
282        return Err(eyre!(
283            "Test {}[{}]: success mismatch: interpreter={}, jit={}",
284            name,
285            idx,
286            interpreter_result.success,
287            jit_result.success
288        ));
289    }
290
291    if interpreter_result.gas_used != jit_result.gas_used {
292        return Err(eyre!(
293            "Test {}[{}]: gas mismatch: interpreter={}, jit={}",
294            name,
295            idx,
296            interpreter_result.gas_used,
297            jit_result.gas_used
298        ));
299    }
300
301    if interpreter_result.output != jit_result.output {
302        return Err(eyre!("Test {}[{}]: output mismatch", name, idx));
303    }
304
305    Ok(())
306}
307
308/// Result from executing a test
309#[derive(Debug)]
310pub struct ExecutionResult {
311    pub success: bool,
312    pub gas_used: u64,
313    pub output: Vec<u8>,
314}
315
316/// Run test with standard revm interpreter at bytecode level.
317///
318/// To match JIT gas accounting, we run the interpreter directly on the bytecode
319/// rather than using full transaction execution which includes intrinsic gas costs.
320fn run_with_interpreter(
321    unit: &TestUnit,
322    tx_env: &TxEnv,
323    db: CacheDB<EmptyDB>,
324    spec_id: SpecId,
325) -> Result<ExecutionResult> {
326    let target = match tx_env.kind {
327        TxKind::Call(addr) => addr,
328        TxKind::Create => {
329            return Ok(ExecutionResult { success: true, gas_used: 0, output: Vec::new() });
330        }
331    };
332
333    let account = match unit.pre.get(&target) {
334        Some(acc) if !acc.code.is_empty() => acc,
335        _ => {
336            return Ok(ExecutionResult { success: true, gas_used: 0, output: Vec::new() });
337        }
338    };
339
340    let gas_limit = tx_env.gas_limit;
341    let bytecode = Bytecode::new_legacy(account.code.clone());
342    let ext_bytecode = ExtBytecode::new(bytecode);
343
344    let input = InputsImpl {
345        target_address: target,
346        bytecode_address: None,
347        caller_address: tx_env.caller,
348        input: revm::interpreter::CallInput::Bytes(tx_env.data.clone()),
349        call_value: tx_env.value,
350    };
351
352    let mut interpreter =
353        Interpreter::new(SharedMemory::new(), ext_bytecode, input, false, spec_id, gas_limit);
354
355    let mut cfg = CfgEnv::default();
356    cfg.spec = spec_id;
357    let block = unit.block_env(&mut cfg);
358    let mut ctx = Context::<BlockEnv, TxEnv, CfgEnv, _, Journal<_>, ()>::new(db, spec_id)
359        .with_block(block)
360        .with_cfg(cfg);
361
362    // Load the target account into the journal (required for SLOAD/SSTORE to work)
363    let _ = ctx.journaled_state.load_account(target);
364    let _ = ctx.journaled_state.load_account(tx_env.caller);
365
366    let table = instruction_table_gas_changes_spec::<EthInterpreter, _>(spec_id);
367    let mut action = interpreter.run_plain(&table, &mut ctx);
368
369    loop {
370        match action {
371            InterpreterAction::Return(result) => {
372                return Ok(ExecutionResult {
373                    success: result.result.is_ok(),
374                    gas_used: interpreter.gas.spent(),
375                    output: result.output.to_vec(),
376                });
377            }
378            InterpreterAction::NewFrame(frame_input) => {
379                let (call_result, return_memory_offset) = match &frame_input {
380                    FrameInput::Call(call_inputs) => {
381                        let offset = call_inputs.return_memory_offset.clone();
382                        // Create child memory context that shares the buffer
383                        let child_memory = interpreter.memory.new_child_context();
384                        let result =
385                            execute_frame_interpreter(&mut ctx, frame_input, spec_id, child_memory);
386                        // Free the child memory context
387                        interpreter.memory.free_child_context();
388                        (result, Some(offset))
389                    }
390                    FrameInput::Create(_) => {
391                        let child_memory = interpreter.memory.new_child_context();
392                        let result =
393                            execute_frame_interpreter(&mut ctx, frame_input, spec_id, child_memory);
394                        interpreter.memory.free_child_context();
395                        (result, None)
396                    }
397                    FrameInput::Empty => {
398                        return Ok(ExecutionResult {
399                            success: false,
400                            gas_used: interpreter.gas.spent(),
401                            output: Vec::new(),
402                        });
403                    }
404                };
405
406                insert_call_outcome(&mut interpreter, call_result, return_memory_offset);
407                action = interpreter.run_plain(&table, &mut ctx);
408            }
409        }
410    }
411}
412
413/// Insert call outcome into interpreter state.
414///
415/// This mimics what revm-handler does in `insert_call_outcome`:
416/// - Push success indicator (1 or 0) onto stack
417/// - Copy return data to memory at the specified offset
418/// - Return unspent gas to parent
419/// - Record refunds on success
420fn insert_call_outcome(
421    interpreter: &mut Interpreter<EthInterpreter>,
422    outcome: InterpreterResult,
423    return_memory_offset: Option<Range<usize>>,
424) {
425    let ins_result = outcome.result;
426    let out_gas = outcome.gas;
427    let returned_len = outcome.output.len();
428
429    interpreter.return_data.set_buffer(outcome.output);
430
431    let success_indicator = if ins_result.is_ok() { U256::from(1) } else { U256::ZERO };
432    let _ = interpreter.stack.push(success_indicator);
433
434    if ins_result.is_ok_or_revert() {
435        interpreter.gas.erase_cost(out_gas.remaining());
436
437        if let Some(mem_range) = return_memory_offset {
438            let target_len = min(mem_range.len(), returned_len);
439            if target_len > 0 {
440                interpreter
441                    .memory
442                    .set(mem_range.start, &interpreter.return_data.buffer()[..target_len]);
443            }
444        }
445    }
446
447    if ins_result.is_ok() {
448        interpreter.gas.record_refund(out_gas.refunded());
449    }
450}
451
452/// Execute a nested call frame using the interpreter.
453fn execute_frame_interpreter<DB: revm::Database>(
454    ctx: &mut Context<BlockEnv, TxEnv, CfgEnv, DB, Journal<DB>, ()>,
455    frame_input: FrameInput,
456    spec_id: SpecId,
457    memory: SharedMemory,
458) -> InterpreterResult
459where
460    DB::Error: std::fmt::Debug,
461{
462    match frame_input {
463        FrameInput::Call(call_inputs) => {
464            let target = call_inputs.bytecode_address;
465            let code_result = ctx.journaled_state.code(target);
466            let Ok(state_load) = code_result else {
467                return InterpreterResult {
468                    result: revm::interpreter::InstructionResult::FatalExternalError,
469                    output: Bytes::new(),
470                    gas: revm::interpreter::Gas::new(0),
471                };
472            };
473            let code_bytes = state_load.data;
474
475            if code_bytes.is_empty() {
476                return InterpreterResult {
477                    result: revm::interpreter::InstructionResult::Stop,
478                    output: Bytes::new(),
479                    gas: revm::interpreter::Gas::new(call_inputs.gas_limit),
480                };
481            }
482
483            let bytecode = Bytecode::new_legacy(code_bytes);
484            let ext_bytecode = ExtBytecode::new(bytecode);
485
486            let input = InputsImpl {
487                target_address: call_inputs.target_address,
488                bytecode_address: Some(call_inputs.bytecode_address),
489                caller_address: call_inputs.caller,
490                input: call_inputs.input.clone(),
491                call_value: match call_inputs.value {
492                    revm::interpreter::CallValue::Transfer(v) => v,
493                    revm::interpreter::CallValue::Apparent(v) => v,
494                },
495            };
496
497            let mut nested_interpreter = Interpreter::new(
498                memory,
499                ext_bytecode,
500                input,
501                call_inputs.is_static,
502                spec_id,
503                call_inputs.gas_limit,
504            );
505
506            let table = instruction_table_gas_changes_spec::<EthInterpreter, _>(spec_id);
507            let mut nested_action = nested_interpreter.run_plain(&table, ctx);
508
509            loop {
510                match nested_action {
511                    InterpreterAction::Return(result) => {
512                        return InterpreterResult {
513                            result: result.result,
514                            output: result.output,
515                            gas: nested_interpreter.gas,
516                        };
517                    }
518                    InterpreterAction::NewFrame(inner_frame) => {
519                        let (inner_result, inner_return_offset) = match &inner_frame {
520                            FrameInput::Call(inner_call) => {
521                                let offset = inner_call.return_memory_offset.clone();
522                                let child_memory = nested_interpreter.memory.new_child_context();
523                                let result = execute_frame_interpreter(
524                                    ctx,
525                                    inner_frame,
526                                    spec_id,
527                                    child_memory,
528                                );
529                                nested_interpreter.memory.free_child_context();
530                                (result, Some(offset))
531                            }
532                            FrameInput::Create(_) => {
533                                let child_memory = nested_interpreter.memory.new_child_context();
534                                let result = execute_frame_interpreter(
535                                    ctx,
536                                    inner_frame,
537                                    spec_id,
538                                    child_memory,
539                                );
540                                nested_interpreter.memory.free_child_context();
541                                (result, None)
542                            }
543                            FrameInput::Empty => {
544                                return InterpreterResult {
545                                    result: revm::interpreter::InstructionResult::Stop,
546                                    output: Bytes::new(),
547                                    gas: nested_interpreter.gas,
548                                };
549                            }
550                        };
551                        insert_call_outcome(
552                            &mut nested_interpreter,
553                            inner_result,
554                            inner_return_offset,
555                        );
556                        nested_action = nested_interpreter.run_plain(&table, ctx);
557                    }
558                }
559            }
560        }
561        FrameInput::Create(create_inputs) => InterpreterResult {
562            result: revm::interpreter::InstructionResult::CreateInitCodeStartingEF00,
563            output: Bytes::new(),
564            gas: revm::interpreter::Gas::new(create_inputs.gas_limit()),
565        },
566        FrameInput::Empty => InterpreterResult {
567            result: revm::interpreter::InstructionResult::Stop,
568            output: Bytes::new(),
569            gas: revm::interpreter::Gas::new(0),
570        },
571    }
572}
573
574/// Run test with JIT-compiled bytecode
575///
576/// This function handles the execution loop for JIT-compiled code, including
577/// processing CALL/CREATE actions that require re-entry.
578fn run_with_jit(
579    unit: &TestUnit,
580    tx_env: &TxEnv,
581    db: CacheDB<EmptyDB>,
582    spec_id: SpecId,
583    compiled: &CompiledContracts,
584) -> Result<ExecutionResult> {
585    let target = match tx_env.kind {
586        TxKind::Call(addr) => addr,
587        TxKind::Create => {
588            return Ok(ExecutionResult { success: true, gas_used: 0, output: Vec::new() });
589        }
590    };
591
592    let account = match unit.pre.get(&target) {
593        Some(acc) if !acc.code.is_empty() => acc,
594        _ => {
595            return Ok(ExecutionResult { success: true, gas_used: 0, output: Vec::new() });
596        }
597    };
598
599    let code_hash = keccak256(&account.code);
600    let Some(jit_fn) = compiled.get(&code_hash) else {
601        return Err(eyre!("No compiled function for target {:x}", target));
602    };
603
604    let gas_limit = tx_env.gas_limit;
605    let bytecode = Bytecode::new_legacy(account.code.clone());
606    let ext_bytecode = ExtBytecode::new(bytecode);
607
608    let input = InputsImpl {
609        target_address: target,
610        bytecode_address: None,
611        caller_address: tx_env.caller,
612        input: revm::interpreter::CallInput::Bytes(tx_env.data.clone()),
613        call_value: tx_env.value,
614    };
615
616    let mut interpreter: Interpreter<EthInterpreter> =
617        Interpreter::new(SharedMemory::new(), ext_bytecode, input, false, spec_id, gas_limit);
618
619    let mut cfg = CfgEnv::default();
620    cfg.spec = spec_id;
621    let block = unit.block_env(&mut cfg);
622    let mut ctx = Context::<BlockEnv, TxEnv, CfgEnv, _, Journal<_>, ()>::new(db, spec_id)
623        .with_block(block)
624        .with_cfg(cfg);
625
626    // Load the target account into the journal (required for SLOAD/SSTORE to work)
627    let _ = ctx.journaled_state.load_account(target);
628    let _ = ctx.journaled_state.load_account(tx_env.caller);
629
630    // Track resume_at across calls
631    let mut resume_at: usize = 0;
632
633    // First call
634    let (result, new_resume_at) =
635        call_jit_with_resume(&mut interpreter, &mut ctx, jit_fn, resume_at);
636    resume_at = new_resume_at;
637
638    let mut last_result = result;
639
640    let mut iteration = 0;
641    loop {
642        iteration += 1;
643        if iteration > 100 {
644            return Err(eyre::eyre!("Too many iterations"));
645        }
646
647        // Check if there's a pending action
648        let action = interpreter.bytecode.action.take();
649
650        match action {
651            Some(InterpreterAction::NewFrame(frame_input)) => {
652                let (call_result, return_memory_offset) = match frame_input {
653                    FrameInput::Call(ref call_inputs) => {
654                        let offset = call_inputs.return_memory_offset.clone();
655                        let result = execute_frame(&mut ctx, frame_input, spec_id, compiled);
656                        (result, Some(offset))
657                    }
658                    FrameInput::Create(_) => {
659                        let result = execute_frame(&mut ctx, frame_input, spec_id, compiled);
660                        (result, None)
661                    }
662                    FrameInput::Empty => {
663                        return Ok(ExecutionResult {
664                            success: false,
665                            gas_used: interpreter.gas.spent(),
666                            output: Vec::new(),
667                        });
668                    }
669                };
670
671                insert_call_outcome(&mut interpreter, call_result, return_memory_offset);
672
673                // Resume with preserved resume_at
674                let (result, new_resume_at) =
675                    call_jit_with_resume(&mut interpreter, &mut ctx, jit_fn, resume_at);
676                resume_at = new_resume_at;
677                last_result = result;
678            }
679            Some(InterpreterAction::Return(ret_result)) => {
680                return Ok(ExecutionResult {
681                    success: ret_result.result.is_ok(),
682                    gas_used: interpreter.gas.spent(),
683                    output: ret_result.output.to_vec(),
684                });
685            }
686            None => {
687                return Ok(ExecutionResult {
688                    success: last_result.is_ok(),
689                    gas_used: interpreter.gas.spent(),
690                    output: Vec::new(),
691                });
692            }
693        }
694    }
695}
696
697/// Call JIT function with explicit resume_at tracking.
698fn call_jit_with_resume<DB: revm::Database + 'static>(
699    interpreter: &mut Interpreter<EthInterpreter>,
700    ctx: &mut Context<BlockEnv, TxEnv, CfgEnv, DB, Journal<DB>, ()>,
701    jit_fn: EvmCompilerFn,
702    resume_at: usize,
703) -> (revm::interpreter::InstructionResult, usize)
704where
705    DB::Error: std::fmt::Debug,
706{
707    interpreter.bytecode.action = None;
708
709    let (stack, stack_len) = EvmStack::from_interpreter_stack(&mut interpreter.stack);
710    let mut ecx = EvmContext {
711        memory: &mut interpreter.memory,
712        input: &mut interpreter.input,
713        gas: &mut interpreter.gas,
714        host: ctx,
715        next_action: &mut interpreter.bytecode.action,
716        return_data: interpreter.return_data.buffer(),
717        is_static: interpreter.runtime_flag.is_static,
718        resume_at,
719    };
720
721    let result = unsafe { jit_fn.call(Some(stack), Some(stack_len), &mut ecx) };
722
723    if result == revm::interpreter::InstructionResult::OutOfGas {
724        ecx.gas.spend_all();
725    }
726
727    let new_resume_at = ecx.resume_at;
728    (result, new_resume_at)
729}
730
731/// Call JIT function with explicit resume_at tracking for nested frames.
732/// This is similar to call_jit_with_resume but accepts a generic Host.
733fn call_jit_with_resume_nested<H: revmc::HostExt>(
734    interpreter: &mut Interpreter<EthInterpreter>,
735    host: &mut H,
736    jit_fn: EvmCompilerFn,
737    resume_at: usize,
738) -> (revm::interpreter::InstructionResult, usize) {
739    interpreter.bytecode.action = None;
740
741    let (stack, stack_len) = EvmStack::from_interpreter_stack(&mut interpreter.stack);
742    let mut ecx = EvmContext {
743        memory: &mut interpreter.memory,
744        input: &mut interpreter.input,
745        gas: &mut interpreter.gas,
746        host,
747        next_action: &mut interpreter.bytecode.action,
748        return_data: interpreter.return_data.buffer(),
749        is_static: interpreter.runtime_flag.is_static,
750        resume_at,
751    };
752
753    let result = unsafe { jit_fn.call(Some(stack), Some(stack_len), &mut ecx) };
754
755    if result == revm::interpreter::InstructionResult::OutOfGas {
756        ecx.gas.spend_all();
757    }
758
759    let new_resume_at = ecx.resume_at;
760    (result, new_resume_at)
761}
762
763/// Execute a nested call or create frame using JIT-compiled code.
764fn execute_frame<DB: revm::Database + 'static>(
765    ctx: &mut Context<BlockEnv, TxEnv, CfgEnv, DB, Journal<DB>, ()>,
766    frame_input: FrameInput,
767    spec_id: SpecId,
768    compiled: &CompiledContracts,
769) -> InterpreterResult
770where
771    DB::Error: std::fmt::Debug,
772{
773    match frame_input {
774        FrameInput::Call(call_inputs) => {
775            let target = call_inputs.bytecode_address;
776            let code_result = ctx.journaled_state.code(target);
777            let Ok(state_load) = code_result else {
778                return InterpreterResult {
779                    result: revm::interpreter::InstructionResult::FatalExternalError,
780                    output: Bytes::new(),
781                    gas: revm::interpreter::Gas::new(0),
782                };
783            };
784            let code_bytes = state_load.data;
785
786            if code_bytes.is_empty() {
787                return InterpreterResult {
788                    result: revm::interpreter::InstructionResult::Stop,
789                    output: Bytes::new(),
790                    gas: revm::interpreter::Gas::new(call_inputs.gas_limit),
791                };
792            }
793
794            let code_hash = keccak256(&code_bytes);
795
796            if let Some(jit_fn) = compiled.get(&code_hash) {
797                let bytecode = Bytecode::new_legacy(code_bytes);
798                let ext_bytecode = ExtBytecode::new(bytecode);
799                let input = InputsImpl {
800                    target_address: call_inputs.target_address,
801                    bytecode_address: Some(call_inputs.bytecode_address),
802                    caller_address: call_inputs.caller,
803                    input: call_inputs.input.clone(),
804                    call_value: match call_inputs.value {
805                        revm::interpreter::CallValue::Transfer(v) => v,
806                        revm::interpreter::CallValue::Apparent(v) => v,
807                    },
808                };
809
810                let mut nested_interpreter = Interpreter::new(
811                    SharedMemory::new(),
812                    ext_bytecode,
813                    input,
814                    call_inputs.is_static,
815                    spec_id,
816                    call_inputs.gas_limit,
817                );
818
819                // Track resume_at across nested call suspensions
820                let mut resume_at: usize = 0;
821
822                // Use call_jit_with_resume_nested instead of call_with_interpreter to properly
823                // track resume_at
824                let (result, new_resume_at) =
825                    call_jit_with_resume_nested(&mut nested_interpreter, ctx, jit_fn, resume_at);
826                resume_at = new_resume_at;
827                let mut last_result = result;
828
829                loop {
830                    let action = nested_interpreter.bytecode.action.take();
831                    match action {
832                        Some(InterpreterAction::Return(result)) => {
833                            return InterpreterResult {
834                                result: result.result,
835                                output: result.output,
836                                gas: nested_interpreter.gas,
837                            };
838                        }
839                        Some(InterpreterAction::NewFrame(inner_frame)) => {
840                            let (inner_result, inner_return_offset) = match inner_frame {
841                                FrameInput::Call(ref inner_call) => {
842                                    let offset = inner_call.return_memory_offset.clone();
843                                    let result = execute_frame(ctx, inner_frame, spec_id, compiled);
844                                    (result, Some(offset))
845                                }
846                                FrameInput::Create(_) => {
847                                    let result = execute_frame(ctx, inner_frame, spec_id, compiled);
848                                    (result, None)
849                                }
850                                FrameInput::Empty => {
851                                    return InterpreterResult {
852                                        result: revm::interpreter::InstructionResult::Stop,
853                                        output: Bytes::new(),
854                                        gas: nested_interpreter.gas,
855                                    };
856                                }
857                            };
858                            insert_call_outcome(
859                                &mut nested_interpreter,
860                                inner_result,
861                                inner_return_offset,
862                            );
863                            // Resume with preserved resume_at
864                            let (result, new_resume_at) = call_jit_with_resume_nested(
865                                &mut nested_interpreter,
866                                ctx,
867                                jit_fn,
868                                resume_at,
869                            );
870                            resume_at = new_resume_at;
871                            last_result = result;
872                        }
873                        None => {
874                            // JIT returned without setting an action - execution is done
875                            return InterpreterResult {
876                                result: last_result,
877                                output: Bytes::new(),
878                                gas: nested_interpreter.gas,
879                            };
880                        }
881                    }
882                }
883            } else {
884                InterpreterResult {
885                    result: revm::interpreter::InstructionResult::Stop,
886                    output: Bytes::new(),
887                    gas: revm::interpreter::Gas::new(call_inputs.gas_limit),
888                }
889            }
890        }
891        FrameInput::Create(create_inputs) => InterpreterResult {
892            result: revm::interpreter::InstructionResult::CreateInitCodeStartingEF00,
893            output: Bytes::new(),
894            gas: revm::interpreter::Gas::new(create_inputs.gas_limit()),
895        },
896        FrameInput::Empty => InterpreterResult {
897            result: revm::interpreter::InstructionResult::Stop,
898            output: Bytes::new(),
899            gas: revm::interpreter::Gas::new(0),
900        },
901    }
902}
903
904/// Run all GeneralStateTests
905pub fn run_general_state_tests(path: &Path) -> Result<Vec<TestResult>> {
906    let test_files = find_json_tests(path);
907    let mut all_results = Vec::new();
908
909    for file in test_files {
910        let suite = match load_test_suite(&file) {
911            Ok(s) => s,
912            Err(e) => {
913                eprintln!("Failed to load {}: {}", file.display(), e);
914                continue;
915            }
916        };
917
918        for (name, unit) in suite.0.iter() {
919            for spec_name in unit.post.keys() {
920                match run_test_unit(name, unit, spec_name) {
921                    Ok(results) => all_results.extend(results),
922                    Err(e) => {
923                        all_results.push(TestResult {
924                            name: name.clone(),
925                            spec: format!("{:?}", spec_name),
926                            passed: false,
927                            error: Some(e.to_string()),
928                        });
929                    }
930                }
931            }
932        }
933    }
934
935    Ok(all_results)
936}
937
938#[cfg(test)]
939mod tests {
940    use super::*;
941    use revm::primitives::Address;
942
943    #[test]
944    fn test_spec_name_conversion() {
945        assert_eq!(spec_name_to_spec_id(&SpecName::Cancun), Some(SpecId::CANCUN));
946        assert_eq!(spec_name_to_spec_id(&SpecName::Constantinople), None);
947    }
948
949    #[test]
950    fn test_build_pre_state() {
951        use revm::primitives::U256;
952        use revm_statetest_types::AccountInfo;
953
954        let mut pre = revm::primitives::HashMap::default();
955        let code: Bytes = vec![0x60, 0x01, 0x00].into();
956        let mut storage = revm::primitives::HashMap::default();
957        storage.insert(U256::from(1), U256::from(42));
958
959        pre.insert(
960            Address::repeat_byte(1),
961            AccountInfo { balance: U256::from(1000), nonce: 5, code, storage },
962        );
963
964        let unit = TestUnit {
965            info: None,
966            env: serde_json::from_str(
967                r#"{
968                "currentCoinbase": "0x0000000000000000000000000000000000000000",
969                "currentDifficulty": "0x0",
970                "currentGasLimit": "0x989680",
971                "currentNumber": "0x0",
972                "currentTimestamp": "0x0",
973                "currentBaseFee": "0x7"
974            }"#,
975            )
976            .unwrap(),
977            pre,
978            post: Default::default(),
979            transaction: Default::default(),
980            out: None,
981        };
982
983        let db = build_pre_state(&unit);
984
985        let acc = db.cache.accounts.get(&Address::repeat_byte(1)).unwrap();
986        assert_eq!(acc.info.balance, U256::from(1000));
987        assert_eq!(acc.info.nonce, 5);
988        assert!(acc.info.code.is_some());
989
990        let storage_val = acc.storage.get(&U256::from(1)).unwrap();
991        assert_eq!(*storage_val, U256::from(42));
992    }
993
994    #[test]
995    fn test_simple_jit_execution() {
996        // Simple bytecode: PUSH1 0x42, PUSH1 0x00, MSTORE, PUSH1 0x20, PUSH1 0x00, RETURN
997        // Returns 0x42 in 32 bytes
998        let bytecode: &[u8] = &[
999            0x60, 0x42, // PUSH1 0x42
1000            0x60, 0x00, // PUSH1 0x00
1001            0x52, // MSTORE
1002            0x60, 0x20, // PUSH1 0x20
1003            0x60, 0x00, // PUSH1 0x00
1004            0xf3, // RETURN
1005        ];
1006
1007        let spec_id = SpecId::CANCUN;
1008        let context = LlvmContext::create();
1009        let backend = EvmLlvmBackend::new(&context, false, OptimizationLevel::Default)
1010            .expect("Failed to create backend");
1011        let mut compiler = EvmCompiler::new(backend);
1012
1013        let jit_fn = unsafe { compiler.jit("test_simple", bytecode, spec_id) }
1014            .expect("Failed to JIT compile");
1015
1016        // Set up interpreter context
1017        let bytecode_obj = Bytecode::new_legacy(bytecode.to_vec().into());
1018        let ext_bytecode = ExtBytecode::new(bytecode_obj);
1019        let input = InputsImpl {
1020            target_address: Address::ZERO,
1021            bytecode_address: None,
1022            caller_address: Address::ZERO,
1023            input: revm::interpreter::CallInput::Bytes(Default::default()),
1024            call_value: Default::default(),
1025        };
1026
1027        let mut interpreter =
1028            Interpreter::new(SharedMemory::new(), ext_bytecode, input, false, spec_id, 100_000);
1029
1030        let mut host = revm::context_interface::host::DummyHost::new(spec_id);
1031        let action = unsafe { jit_fn.call_with_interpreter(&mut interpreter, &mut host) };
1032
1033        match action {
1034            revm::interpreter::InterpreterAction::Return(result) => {
1035                assert!(result.result.is_ok(), "Expected success, got {:?}", result.result);
1036                assert_eq!(result.output.len(), 32);
1037                assert_eq!(result.output[31], 0x42);
1038            }
1039            other => panic!("Expected Return action, got {:?}", other),
1040        }
1041    }
1042
1043    #[test]
1044    fn test_compile_multiple_bytecodes() {
1045        // Test that we can compile multiple different bytecodes
1046        let code1: &[u8] = &[0x60, 0x01, 0x60, 0x02, 0x01, 0x00]; // PUSH1 1, PUSH1 2, ADD, STOP
1047        let code2: &[u8] = &[0x60, 0x03, 0x60, 0x04, 0x02, 0x00]; // PUSH1 3, PUSH1 4, MUL, STOP
1048
1049        let context = LlvmContext::create();
1050        let backend = EvmLlvmBackend::new(&context, false, OptimizationLevel::Default)
1051            .expect("Failed to create backend");
1052        let mut compiler = EvmCompiler::new(backend);
1053
1054        // Phase 1: Translate both contracts BEFORE finalization
1055        let id1 = compiler
1056            .translate("contract1", code1, SpecId::CANCUN)
1057            .expect("Failed to translate contract 1");
1058        let id2 = compiler
1059            .translate("contract2", code2, SpecId::CANCUN)
1060            .expect("Failed to translate contract 2");
1061
1062        // Phase 2: JIT both functions (first call finalizes the module)
1063        let fn1 = unsafe { compiler.jit_function(id1) }.expect("Failed to JIT contract 1");
1064        let fn2 = unsafe { compiler.jit_function(id2) }.expect("Failed to JIT contract 2");
1065
1066        // Both functions should be valid (different addresses)
1067        assert_ne!(fn1.into_inner() as usize, fn2.into_inner() as usize);
1068    }
1069
1070    #[test]
1071    #[ignore = "requires ethereum/tests checkout"]
1072    fn test_run_general_state_tests() {
1073        let mut path = get_ethtests_path().join("GeneralStateTests");
1074
1075        // Allow running a specific subdirectory via SUBDIR env var
1076        if let Ok(subdir) = env::var("SUBDIR") {
1077            path = path.join(subdir);
1078        }
1079
1080        if !path.exists() {
1081            eprintln!("Skipping: {} does not exist", path.display());
1082            return;
1083        }
1084
1085        let results = run_general_state_tests(&path).unwrap();
1086        let passed = results.iter().filter(|r| r.passed).count();
1087        let failed = results.iter().filter(|r| !r.passed).count();
1088        println!("Passed: {}, Failed: {}", passed, failed);
1089
1090        for result in results.iter().filter(|r| !r.passed) {
1091            println!("FAILED: {} ({}): {:?}", result.name, result.spec, result.error);
1092        }
1093    }
1094}