Skip to main content

revmc_runtime/
revm_evm.rs

1//! Generic `revm` JIT EVM.
2//!
3//! Provides [`JitEvm`] which wraps any mainnet-shaped [`EvmTr`]-based EVM and overrides
4//! execution to look up compiled functions via [`JitBackend`] before falling
5//! back to the interpreter.
6
7use crate::runtime::{JitBackend, LookupDecision, LookupRequest, RuntimeCacheKey};
8use alloy_primitives::{Address, Bytes};
9use revm_context::{Host, JournalEntry};
10use revm_context_interface::{
11    Cfg, ContextSetters, ContextTr, Database,
12    journaled_state::JournalTr,
13    result::{EVMError, HaltReason, InvalidTransaction, ResultAndState},
14};
15use revm_database_interface::DatabaseCommit;
16use revm_handler::{
17    EthFrame, EvmTr, ExecuteCommitEvm, ExecuteEvm, FrameInitOrResult, FrameTr, Handler,
18    ItemOrResult, MainnetHandler, PrecompileProvider, SystemCallCommitEvm, SystemCallEvm,
19    SystemCallTx, evm::ContextDbError,
20};
21use revm_inspector::{
22    InspectCommitEvm, InspectEvm, InspectSystemCallEvm, Inspector, InspectorEvmTr, InspectorFrame,
23    InspectorHandler, JournalExt,
24};
25use revm_interpreter::{InstructionResult, InterpreterAction, InterpreterResult, InterpreterTypes};
26use revm_primitives::{B256Map, hardfork::SpecId};
27use revm_state::EvmState;
28use std::sync::Arc;
29
30/// Mainnet EVM with JIT-compiled function dispatch.
31///
32/// Wraps an inner [`EvmTr`] and uses [`JitBackend`] to look up compiled
33/// functions before falling back to the interpreter.
34///
35/// Implements [`ExecuteEvm`] so it can be used as a drop-in replacement for
36/// the standard mainnet EVM.
37#[derive(derive_more::Debug)]
38pub struct JitEvm<EVM> {
39    inner: EVM,
40    backend: JitBackend,
41    /// Cached lookup decisions keyed by `code_hash` alone.
42    /// Invalidated when the `spec_id` changes.
43    #[debug(skip)]
44    lookup_cache: B256Map<LookupDecision>,
45    /// The `spec_id` the cache was built for; cleared on mismatch.
46    lookup_cache_spec_id: SpecId,
47}
48
49impl<EVM> JitEvm<EVM> {
50    /// Returns a reference to the inner EVM.
51    pub const fn inner(&self) -> &EVM {
52        &self.inner
53    }
54
55    /// Returns a mutable reference to the inner EVM.
56    pub fn inner_mut(&mut self) -> &mut EVM {
57        &mut self.inner
58    }
59
60    /// Consumes the JIT EVM and returns the inner EVM.
61    pub fn into_inner(self) -> EVM {
62        self.inner
63    }
64
65    /// Returns a reference to the JIT backend.
66    pub const fn backend(&self) -> &JitBackend {
67        &self.backend
68    }
69
70    /// Returns a mutable reference to the JIT backend.
71    pub fn backend_mut(&mut self) -> &mut JitBackend {
72        &mut self.backend
73    }
74
75    /// Replaces the JIT backend and clears the lookup cache.
76    pub fn set_backend(&mut self, backend: JitBackend) {
77        self.backend = backend;
78        self.lookup_cache.clear();
79    }
80}
81
82impl<EVM: EvmTr> JitEvm<EVM>
83where
84    EVM::Context: ContextTr,
85{
86    /// Creates a new JIT EVM with a disabled backend.
87    ///
88    /// Equivalent to `JitEvm::new(inner, JitBackend::disabled())`.
89    pub fn disabled(inner: EVM) -> Self {
90        Self::new(inner, JitBackend::disabled())
91    }
92
93    /// Creates a new JIT EVM from an inner EVM and backend.
94    pub fn new(inner: EVM, backend: JitBackend) -> Self {
95        let spec_id: SpecId = inner.ctx_ref().cfg().spec().into();
96        Self {
97            inner,
98            backend,
99            lookup_cache: B256Map::with_capacity_and_hasher(16, Default::default()),
100            lookup_cache_spec_id: spec_id,
101        }
102    }
103
104    fn invalidate_cache(&mut self) {
105        let spec_id: SpecId = self.inner.ctx_ref().cfg().spec().into();
106        if spec_id != self.lookup_cache_spec_id {
107            self.lookup_cache.clear();
108            self.lookup_cache_spec_id = spec_id;
109            // } else {
110            //     self.lookup_cache.retain(|_, v| matches!(v, LookupDecision::Compiled(_)));
111        }
112    }
113}
114
115impl<EVM> core::ops::Deref for JitEvm<EVM> {
116    type Target = EVM;
117
118    #[inline]
119    fn deref(&self) -> &EVM {
120        &self.inner
121    }
122}
123
124impl<EVM> core::ops::DerefMut for JitEvm<EVM> {
125    #[inline]
126    fn deref_mut(&mut self) -> &mut EVM {
127        &mut self.inner
128    }
129}
130
131impl<EVM> EvmTr for JitEvm<EVM>
132where
133    EVM: EvmTr<
134            Frame = EthFrame,
135            Precompiles: PrecompileProvider<EVM::Context, Output = InterpreterResult>,
136        >,
137{
138    type Context = EVM::Context;
139    type Instructions = EVM::Instructions;
140    type Precompiles = EVM::Precompiles;
141    type Frame = EVM::Frame;
142
143    #[inline]
144    fn all(
145        &self,
146    ) -> (
147        &Self::Context,
148        &Self::Instructions,
149        &Self::Precompiles,
150        &revm_context_interface::FrameStack<Self::Frame>,
151    ) {
152        self.inner.all()
153    }
154
155    #[inline]
156    fn all_mut(
157        &mut self,
158    ) -> (
159        &mut Self::Context,
160        &mut Self::Instructions,
161        &mut Self::Precompiles,
162        &mut revm_context_interface::FrameStack<Self::Frame>,
163    ) {
164        self.inner.all_mut()
165    }
166
167    #[inline]
168    fn frame_init(
169        &mut self,
170        frame_input: <Self::Frame as FrameTr>::FrameInit,
171    ) -> Result<
172        ItemOrResult<&mut Self::Frame, <Self::Frame as FrameTr>::FrameResult>,
173        ContextDbError<Self::Context>,
174    > {
175        self.inner.frame_init(frame_input)
176    }
177
178    #[inline]
179    fn frame_run(
180        &mut self,
181    ) -> Result<FrameInitOrResult<Self::Frame>, ContextDbError<Self::Context>> {
182        let spec_id: SpecId = self.inner.ctx_ref().cfg().spec().into();
183        let frame = self.inner.frame_stack().get();
184        let code_hash = frame.interpreter.bytecode.get_or_calculate_hash();
185
186        let decision = self.lookup_cache.entry(code_hash).or_insert_with(|| {
187            let code = frame.interpreter.bytecode.original_bytes();
188            self.backend.lookup(LookupRequest { key: RuntimeCacheKey { code_hash, spec_id }, code })
189        });
190
191        Ok(match decision {
192            LookupDecision::Compiled(program) => {
193                let (ctx, _, _, frame_stack) = self.inner.all_mut();
194                let frame = frame_stack.get();
195                let action =
196                    unsafe { program.func.call_with_interpreter(&mut frame.interpreter, ctx) };
197                frame.process_next_action::<_, ContextDbError<Self::Context>>(ctx, action).inspect(
198                    |i| {
199                        if i.is_result() {
200                            frame.set_finished(true);
201                        }
202                    },
203                )?
204            }
205            LookupDecision::Interpret(_) => self.inner.frame_run()?,
206        })
207    }
208
209    #[inline]
210    fn frame_return_result(
211        &mut self,
212        result: <Self::Frame as FrameTr>::FrameResult,
213    ) -> Result<Option<<Self::Frame as FrameTr>::FrameResult>, ContextDbError<Self::Context>> {
214        self.inner.frame_return_result(result)
215    }
216}
217
218impl<EVM> ExecuteEvm for JitEvm<EVM>
219where
220    EVM: EvmTr<
221            Frame = EthFrame,
222            Context: ContextTr<Journal: JournalTr<State = EvmState>> + ContextSetters,
223            Precompiles: PrecompileProvider<EVM::Context, Output = InterpreterResult>,
224        >,
225{
226    type ExecutionResult = revm_context_interface::result::ExecutionResult<HaltReason>;
227    type State = EvmState;
228    type Error = EVMError<<<EVM::Context as ContextTr>::Db as Database>::Error, InvalidTransaction>;
229    type Tx = <EVM::Context as ContextTr>::Tx;
230    type Block = <EVM::Context as ContextTr>::Block;
231
232    #[inline]
233    fn transact_one(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
234        self.ctx_mut().set_tx(tx);
235        self.invalidate_cache();
236        MainnetHandler::default().run(self)
237    }
238
239    #[inline]
240    fn finalize(&mut self) -> Self::State {
241        self.ctx_mut().journal_mut().finalize()
242    }
243
244    #[inline]
245    fn set_block(&mut self, block: Self::Block) {
246        self.ctx_mut().set_block(block);
247    }
248
249    #[inline]
250    fn replay(&mut self) -> Result<ResultAndState<HaltReason>, Self::Error> {
251        self.invalidate_cache();
252        MainnetHandler::default().run(self).map(|result| {
253            let state = self.ctx_mut().journal_mut().finalize();
254            ResultAndState::new(result, state)
255        })
256    }
257}
258
259impl<EVM> ExecuteCommitEvm for JitEvm<EVM>
260where
261    Self: ExecuteEvm<State = EvmState>,
262    EVM: EvmTr<Context: ContextTr<Db: DatabaseCommit>>,
263{
264    #[inline]
265    fn commit(&mut self, state: Self::State) {
266        self.ctx_mut().db_mut().commit(state);
267    }
268}
269
270impl<EVM> SystemCallEvm for JitEvm<EVM>
271where
272    EVM: EvmTr<
273            Frame = EthFrame,
274            Context: ContextTr<Journal: JournalTr<State = EvmState>, Tx: SystemCallTx>
275                         + ContextSetters,
276            Precompiles: PrecompileProvider<EVM::Context, Output = InterpreterResult>,
277        >,
278{
279    #[inline]
280    fn system_call_one_with_caller(
281        &mut self,
282        caller: Address,
283        system_contract_address: Address,
284        data: Bytes,
285    ) -> Result<Self::ExecutionResult, Self::Error> {
286        self.ctx_mut().set_tx(
287            <<EVM::Context as ContextTr>::Tx as SystemCallTx>::new_system_tx_with_caller(
288                caller,
289                system_contract_address,
290                data,
291            ),
292        );
293        self.invalidate_cache();
294        MainnetHandler::default().run_system_call(self)
295    }
296}
297
298impl<EVM> SystemCallCommitEvm for JitEvm<EVM>
299where
300    Self: SystemCallEvm<State = EvmState> + ExecuteCommitEvm,
301    EVM: EvmTr<Context: ContextTr<Db: DatabaseCommit>>,
302{
303    #[inline]
304    fn system_call_with_caller_commit(
305        &mut self,
306        caller: Address,
307        system_contract_address: Address,
308        data: Bytes,
309    ) -> Result<Self::ExecutionResult, Self::Error> {
310        self.system_call_with_caller(caller, system_contract_address, data).map(|output| {
311            self.ctx_mut().db_mut().commit(output.state);
312            output.result
313        })
314    }
315}
316
317impl<EVM> InspectorEvmTr for JitEvm<EVM>
318where
319    EVM: EvmTr<
320            Frame = EthFrame,
321            Precompiles: PrecompileProvider<EVM::Context, Output = InterpreterResult>,
322        > + InspectorEvmTr,
323{
324    type Inspector = <EVM as InspectorEvmTr>::Inspector;
325
326    #[inline]
327    fn all_inspector(
328        &self,
329    ) -> (
330        &Self::Context,
331        &Self::Instructions,
332        &Self::Precompiles,
333        &revm_context_interface::FrameStack<Self::Frame>,
334        &Self::Inspector,
335    ) {
336        self.inner.all_inspector()
337    }
338
339    #[inline]
340    fn all_mut_inspector(
341        &mut self,
342    ) -> (
343        &mut Self::Context,
344        &mut Self::Instructions,
345        &mut Self::Precompiles,
346        &mut revm_context_interface::FrameStack<Self::Frame>,
347        &mut Self::Inspector,
348    ) {
349        self.inner.all_mut_inspector()
350    }
351
352    #[inline]
353    fn inspect_frame_run(
354        &mut self,
355    ) -> Result<FrameInitOrResult<Self::Frame>, ContextDbError<Self::Context>> {
356        let spec_id: SpecId = self.inner.ctx_ref().cfg().spec().into();
357        let frame = self.inner.frame_stack().get();
358        let code_hash = frame.interpreter.bytecode.get_or_calculate_hash();
359
360        let decision = self.lookup_cache.entry(code_hash).or_insert_with(|| {
361            let code = frame.interpreter.bytecode.original_bytes();
362            self.backend.lookup(LookupRequest { key: RuntimeCacheKey { code_hash, spec_id }, code })
363        });
364
365        let program = match decision {
366            LookupDecision::Compiled(program) => Arc::clone(program),
367            LookupDecision::Interpret(_) => return self.inner.inspect_frame_run(),
368        };
369
370        // Set up the on_log callback to forward logs to the inspector during JIT execution.
371        //
372        // SAFETY: We dereference raw pointers to the inspector and context inside the closure.
373        // This creates aliasing &mut references with `ecx.host` (which borrows the context),
374        // but the inspector's `log()` implementation only accesses the inspector itself,
375        // not the journaled state already borrowed by the host.
376        let (ctx, inspector) = self.ctx_inspector();
377        let ctx_ptr: *mut EVM::Context = ctx;
378        let inspector_ptr: *mut <EVM as InspectorEvmTr>::Inspector = inspector;
379        let mut on_log = move |log: &revm_primitives::Log| unsafe {
380            (*inspector_ptr).log(&mut *ctx_ptr, log.clone());
381        };
382
383        let (ctx, _, _, frame_stack) = self.inner.all_mut();
384        let frame = frame_stack.get();
385        let action = unsafe {
386            program.func.call_with_interpreter_with(&mut frame.interpreter, ctx, |ecx| {
387                // SAFETY: `on_log` lives on the stack and outlives the JIT call.
388                // The closure captures raw pointers whose types may not be
389                // `'static`, so we erase the lifetime via pointer cast.
390                ecx.on_log = Some(core::mem::transmute::<
391                    &mut dyn FnMut(&revm_primitives::Log),
392                    &mut (dyn FnMut(&revm_primitives::Log) + '_),
393                >(&mut on_log));
394            })
395        };
396
397        // Handle selfdestruct.
398        let (ctx, inspector) = self.ctx_inspector();
399        if let InterpreterAction::Return(result) = &action
400            && result.result == InstructionResult::SelfDestruct
401        {
402            inspect_selfdestruct(ctx, inspector);
403        }
404
405        let (ctx, _, _, frame_stack) = self.inner.all_mut();
406        let frame = frame_stack.get();
407        let mut result = frame.process_next_action::<_, ContextDbError<Self::Context>>(ctx, action);
408
409        if let Ok(ItemOrResult::Result(frame_result)) = &mut result {
410            let (ctx, inspector, frame) = self.ctx_inspector_frame();
411            if let Some(frame) = frame.eth_frame() {
412                revm_inspector::handler::frame_end(ctx, inspector, &frame.input, frame_result);
413                frame.set_finished(true);
414            }
415        }
416        result
417    }
418}
419
420impl<EVM> InspectEvm for JitEvm<EVM>
421where
422    EVM: EvmTr<
423            Frame = EthFrame,
424            Context: ContextTr<Journal: JournalTr<State = EvmState> + JournalExt> + ContextSetters,
425            Precompiles: PrecompileProvider<EVM::Context, Output = InterpreterResult>,
426        > + InspectorEvmTr,
427{
428    type Inspector = <EVM as InspectorEvmTr>::Inspector;
429
430    #[inline]
431    fn set_inspector(&mut self, inspector: Self::Inspector) {
432        *self.inner.inspector() = inspector;
433    }
434
435    #[inline]
436    fn inspect_one_tx(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
437        self.inner.ctx_mut().set_tx(tx);
438        self.invalidate_cache();
439        MainnetHandler::default().inspect_run(self)
440    }
441}
442
443impl<EVM> InspectCommitEvm for JitEvm<EVM> where Self: InspectEvm + ExecuteCommitEvm {}
444
445impl<EVM> InspectSystemCallEvm for JitEvm<EVM>
446where
447    EVM: EvmTr<
448            Frame = EthFrame,
449            Context: ContextTr<
450                Journal: JournalTr<State = EvmState> + JournalExt,
451                Tx: SystemCallTx,
452            > + ContextSetters,
453            Precompiles: PrecompileProvider<EVM::Context, Output = InterpreterResult>,
454        > + InspectorEvmTr,
455{
456    #[inline]
457    fn inspect_one_system_call_with_caller(
458        &mut self,
459        caller: Address,
460        system_contract_address: Address,
461        data: Bytes,
462    ) -> Result<Self::ExecutionResult, Self::Error> {
463        self.inner.ctx_mut().set_tx(
464            <<EVM::Context as ContextTr>::Tx as SystemCallTx>::new_system_tx_with_caller(
465                caller,
466                system_contract_address,
467                data,
468            ),
469        );
470        self.invalidate_cache();
471        MainnetHandler::default().inspect_run_system_call(self)
472    }
473}
474
475#[inline(never)]
476#[cold]
477fn inspect_selfdestruct<CTX, IT>(context: &mut CTX, inspector: &mut impl Inspector<CTX, IT>)
478where
479    CTX: ContextTr<Journal: JournalExt> + Host,
480    IT: InterpreterTypes,
481{
482    if let Some(
483        JournalEntry::AccountDestroyed {
484            address: contract, target: to, had_balance: balance, ..
485        }
486        | JournalEntry::BalanceTransfer { from: contract, to, balance, .. },
487    ) = context.journal_mut().journal().last()
488    {
489        inspector.selfdestruct(*contract, *to, *balance);
490    }
491}
492
493#[cfg(test)]
494#[cfg(feature = "llvm")]
495mod tests {
496    use super::*;
497    use crate::runtime::{JitBackend, RuntimeConfig};
498    use alloy_primitives::{Address, Bytes, TxKind, U256};
499    use revm_bytecode::opcode as op;
500    use revm_context::TxEnv;
501    use revm_context_interface::result::{ExecutionResult, Output};
502    use revm_database::{CacheDB, EmptyDB};
503    use revm_handler::MainBuilder;
504
505    fn blocking_backend() -> JitBackend {
506        JitBackend::new(RuntimeConfig { blocking: true, ..Default::default() }).unwrap()
507    }
508
509    type TestInnerEvm = revm_handler::MainnetEvm<
510        revm_context::Context<
511            revm_context::BlockEnv,
512            TxEnv,
513            revm_context::CfgEnv,
514            CacheDB<EmptyDB>,
515        >,
516    >;
517
518    fn test_jit_evm_with_spec(backend: JitBackend, spec_id: SpecId) -> JitEvm<TestInnerEvm> {
519        let inner = revm_context::Context::new(CacheDB::default(), spec_id).build_mainnet();
520        JitEvm::new(inner, backend)
521    }
522
523    fn test_jit_evm(backend: JitBackend) -> JitEvm<TestInnerEvm> {
524        test_jit_evm_with_spec(backend, SpecId::CANCUN)
525    }
526
527    fn deploy_contract_with_nonce(
528        evm: &mut JitEvm<TestInnerEvm>,
529        bytecode: &[u8],
530        nonce: u64,
531    ) -> Address {
532        let len = bytecode.len();
533        assert!(len <= 255);
534        let offset = 10u8;
535        let mut deploy_code = vec![
536            op::PUSH1,
537            len as u8,
538            op::PUSH1,
539            offset,
540            op::PUSH0,
541            op::CODECOPY,
542            op::PUSH1,
543            len as u8,
544            op::PUSH0,
545            op::RETURN,
546        ];
547        deploy_code.extend_from_slice(bytecode);
548
549        let tx = TxEnv {
550            kind: TxKind::Create,
551            nonce,
552            data: Bytes::copy_from_slice(&deploy_code),
553            gas_limit: 1_000_000,
554            ..Default::default()
555        };
556
557        let result = evm.transact_commit(tx).unwrap();
558        match result {
559            ExecutionResult::Success { output, .. } => match output {
560                Output::Create(_, Some(addr)) => addr,
561                other => panic!("expected Create output, got: {other:?}"),
562            },
563            other => panic!("expected Success, got: {other:?}"),
564        }
565    }
566
567    fn deploy_contract(evm: &mut JitEvm<TestInnerEvm>, bytecode: &[u8]) -> Address {
568        deploy_contract_with_nonce(evm, bytecode, 0)
569    }
570
571    fn call_contract(backend: JitBackend, spec_id: SpecId, bytecode: &[u8]) -> ExecutionResult {
572        let mut evm = test_jit_evm_with_spec(backend, spec_id);
573        let contract_addr = deploy_contract(&mut evm, bytecode);
574        let tx = TxEnv {
575            kind: TxKind::Call(contract_addr),
576            nonce: 1,
577            gas_limit: 1_000_000,
578            ..Default::default()
579        };
580        evm.transact(tx).unwrap().result
581    }
582
583    #[test]
584    fn jit_evm_simple_return() {
585        let backend = blocking_backend();
586
587        // PUSH1 0x42 PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN
588        let runtime_code: &[u8] = &[0x60, 0x42, 0x5f, 0x52, 0x60, 0x20, 0x5f, 0xf3];
589
590        let mut evm = test_jit_evm(backend.clone());
591        let contract_addr = deploy_contract(&mut evm, runtime_code);
592
593        let tx = TxEnv {
594            kind: TxKind::Call(contract_addr),
595            nonce: 1,
596            gas_limit: 1_000_000,
597            ..Default::default()
598        };
599
600        let result = evm.transact(tx).unwrap();
601        match result.result {
602            ExecutionResult::Success { output, .. } => {
603                let data = match &output {
604                    Output::Call(bytes) => bytes,
605                    other => panic!("expected Call output, got: {other:?}"),
606                };
607                assert_eq!(U256::from_be_slice(data), U256::from(0x42));
608            }
609            other => panic!("expected Success, got: {other:?}"),
610        }
611
612        let stats = backend.stats();
613        assert!(stats.compilations_succeeded > 0, "expected compilations, got: {stats:?}");
614        assert!(stats.resident_entries > 0, "expected resident entries, got: {stats:?}");
615    }
616
617    #[test]
618    fn jit_evm_add() {
619        let backend = blocking_backend();
620
621        // PUSH1 1 PUSH1 2 ADD PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN
622        let runtime_code: &[u8] =
623            &[0x60, 0x01, 0x60, 0x02, 0x01, 0x5f, 0x52, 0x60, 0x20, 0x5f, 0xf3];
624
625        let mut evm = test_jit_evm(backend.clone());
626        let contract_addr = deploy_contract(&mut evm, runtime_code);
627
628        let tx = TxEnv {
629            kind: TxKind::Call(contract_addr),
630            nonce: 1,
631            gas_limit: 1_000_000,
632            ..Default::default()
633        };
634
635        let result = evm.transact(tx).unwrap();
636        match result.result {
637            ExecutionResult::Success { output, .. } => {
638                let data = match &output {
639                    Output::Call(bytes) => bytes,
640                    other => panic!("expected Call output, got: {other:?}"),
641                };
642                assert_eq!(U256::from_be_slice(data), U256::from(3));
643            }
644            other => panic!("expected Success, got: {other:?}"),
645        }
646
647        let stats = backend.stats();
648        assert!(stats.compilations_succeeded > 0, "expected compilations, got: {stats:?}");
649        assert!(stats.resident_entries > 0, "expected resident entries, got: {stats:?}");
650    }
651
652    #[test]
653    fn jit_evm_fallback_empty_code() {
654        let backend = blocking_backend();
655        let mut evm = test_jit_evm(backend);
656
657        let tx = TxEnv {
658            kind: TxKind::Call(Address::with_last_byte(0xEE)),
659            gas_limit: 1_000_000,
660            ..Default::default()
661        };
662
663        let result = evm.transact(tx).unwrap();
664        assert!(
665            matches!(result.result, ExecutionResult::Success { .. }),
666            "expected Success for empty-code call, got: {:?}",
667            result.result,
668        );
669    }
670
671    #[test]
672    fn jit_evm_amsterdam_state_gas_failure_matches_interpreter_gas() {
673        // PUSH1 1 PUSH1 0xea SSTORE ADD
674        //
675        // In Amsterdam, the SSTORE charges EIP-8037 state gas. The following ADD
676        // stack-underflows. Compiled failures may collapse to a single halt code,
677        // but final transaction gas accounting still has to match the interpreter.
678        let bytecode = &[op::PUSH1, 0x01, op::PUSH1, 0xea, op::SSTORE, op::ADD];
679
680        let interpreter = call_contract(JitBackend::disabled(), SpecId::AMSTERDAM, bytecode);
681        let jit = call_contract(blocking_backend(), SpecId::AMSTERDAM, bytecode);
682
683        let (
684            ExecutionResult::Halt { gas: jit_gas, .. },
685            ExecutionResult::Halt { gas: interpreter_gas, .. },
686        ) = (&jit, &interpreter)
687        else {
688            panic!("expected halt results: jit={jit:?}, interpreter={interpreter:?}");
689        };
690        assert_eq!(jit_gas, interpreter_gas);
691    }
692
693    /// Non-blocking mode: JIT compiles in background and results eventually appear.
694    #[test]
695    fn jit_evm_non_blocking() {
696        use crate::runtime::RuntimeTuning;
697
698        let config = RuntimeConfig {
699            enabled: true,
700            tuning: RuntimeTuning {
701                jit_hot_threshold: 1,
702                jit_worker_count: 1,
703                ..Default::default()
704            },
705            ..Default::default()
706        };
707        let backend = JitBackend::new(config).unwrap();
708
709        let runtime_code: &[u8] = &[0x60, 0x42, 0x5f, 0x52, 0x60, 0x20, 0x5f, 0xf3];
710
711        let mut evm = test_jit_evm(backend.clone());
712        let contract_addr = deploy_contract(&mut evm, runtime_code);
713
714        // First call: should succeed (interpreter fallback or JIT).
715        let tx = TxEnv {
716            kind: TxKind::Call(contract_addr),
717            nonce: 1,
718            gas_limit: 1_000_000,
719            ..Default::default()
720        };
721        let result = evm.transact(tx).unwrap();
722        assert!(matches!(result.result, ExecutionResult::Success { .. }));
723
724        // Wait for JIT to compile in background.
725        let code_hash = alloy_primitives::keccak256(runtime_code);
726        let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
727        loop {
728            if backend.get_compiled(code_hash, SpecId::CANCUN).is_some() {
729                break;
730            }
731            assert!(std::time::Instant::now() < deadline, "timed out waiting for JIT");
732            std::thread::sleep(std::time::Duration::from_millis(50));
733        }
734
735        // Second call: should use JIT-compiled function.
736        let mut evm = test_jit_evm(backend);
737        let contract_addr = deploy_contract(&mut evm, runtime_code);
738
739        let tx = TxEnv {
740            kind: TxKind::Call(contract_addr),
741            nonce: 1,
742            gas_limit: 1_000_000,
743            ..Default::default()
744        };
745        let result = evm.transact(tx).unwrap();
746        match result.result {
747            ExecutionResult::Success { output, .. } => {
748                let data = match &output {
749                    Output::Call(bytes) => bytes,
750                    other => panic!("expected Call output, got: {other:?}"),
751                };
752                assert_eq!(U256::from_be_slice(data), U256::from(0x42));
753            }
754            other => panic!("expected Success, got: {other:?}"),
755        }
756    }
757
758    /// CALL into another contract: JIT handles the nested call frame.
759    #[test]
760    fn jit_evm_nested_call() {
761        let backend = blocking_backend();
762        let mut evm = test_jit_evm(backend);
763
764        // Inner contract: PUSH1 0x42 PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN
765        let inner_code: &[u8] = &[0x60, 0x42, 0x5f, 0x52, 0x60, 0x20, 0x5f, 0xf3];
766        let inner_addr = deploy_contract_with_nonce(&mut evm, inner_code, 0);
767
768        // Outer contract: CALL inner, then copy return data and return it.
769        let mut outer_code = vec![
770            op::PUSH0, // retLen
771            op::PUSH0, // retOff
772            op::PUSH0, // argsLen
773            op::PUSH0, // argsOff
774            op::PUSH0, // value
775        ];
776        // PUSH20 <inner_addr>
777        outer_code.push(op::PUSH20);
778        outer_code.extend_from_slice(inner_addr.as_slice());
779        outer_code.extend_from_slice(&[
780            op::PUSH2,
781            0xFF,
782            0xFF, // gas
783            op::CALL,
784            op::RETURNDATASIZE,
785            op::PUSH0,
786            op::PUSH0,
787            op::RETURNDATACOPY,
788            op::RETURNDATASIZE,
789            op::PUSH0,
790            op::RETURN,
791        ]);
792        let outer_addr = deploy_contract_with_nonce(&mut evm, &outer_code, 1);
793
794        let tx = TxEnv {
795            kind: TxKind::Call(outer_addr),
796            nonce: 2,
797            gas_limit: 1_000_000,
798            ..Default::default()
799        };
800        let result = evm.transact(tx).unwrap();
801        match result.result {
802            ExecutionResult::Success { output, .. } => {
803                let data = match &output {
804                    Output::Call(bytes) => bytes,
805                    other => panic!("expected Call output, got: {other:?}"),
806                };
807                assert_eq!(U256::from_be_slice(data), U256::from(0x42));
808            }
809            other => panic!("expected Success, got: {other:?}"),
810        }
811    }
812
813    /// CREATE2 factory: deploy the same runtime code multiple times with different salts.
814    /// All deployments share the same code hash and should hit the JIT cache.
815    #[test]
816    fn jit_evm_create2_factory() {
817        let backend = blocking_backend();
818        let mut evm = test_jit_evm(backend.clone());
819
820        // Runtime code: PUSH1 0xAB PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN
821        let runtime_code: &[u8] = &[0x60, 0xAB, 0x5f, 0x52, 0x60, 0x20, 0x5f, 0xf3];
822
823        // Build init code that deploys `runtime_code` (same pattern as deploy_contract_with_nonce).
824        let rt_len = runtime_code.len();
825        let init_offset = 10u8;
826        let mut init_code = vec![
827            op::PUSH1,
828            rt_len as u8,
829            op::PUSH1,
830            init_offset,
831            op::PUSH0,
832            op::CODECOPY,
833            op::PUSH1,
834            rt_len as u8,
835            op::PUSH0,
836            op::RETURN,
837        ];
838        init_code.extend_from_slice(runtime_code);
839        let init_len = init_code.len();
840
841        // Factory contract: copies embedded init_code to memory, reads salt from
842        // calldata, then CREATE2. Returns the deployed address.
843        let mut factory_code = vec![
844            op::PUSH1,
845            0, // placeholder for init_len
846            op::PUSH1,
847            0, // placeholder for code_offset
848            op::PUSH0,
849            op::CODECOPY, // mem[0..init_len] = init_code
850            op::PUSH0,
851            op::CALLDATALOAD, // salt from calldata[0..32]
852            op::PUSH1,
853            0,         // placeholder for init_len
854            op::PUSH0, // offset
855            op::PUSH0, // value
856            op::CREATE2,
857            op::PUSH0,
858            op::MSTORE,
859            op::PUSH1,
860            0x20,
861            op::PUSH0,
862            op::RETURN,
863        ];
864        let code_offset = factory_code.len() as u8;
865        factory_code[1] = init_len as u8;
866        factory_code[3] = code_offset;
867        factory_code[9] = init_len as u8;
868        factory_code.extend_from_slice(&init_code);
869
870        let factory_addr = deploy_contract(&mut evm, &factory_code);
871
872        // Deploy 3 instances via the factory with different salts.
873        let mut deployed_addrs = Vec::new();
874        for (i, salt) in (1u64..=3).enumerate() {
875            let mut calldata = [0u8; 32];
876            calldata[24..].copy_from_slice(&salt.to_be_bytes());
877
878            let tx = TxEnv {
879                kind: TxKind::Call(factory_addr),
880                nonce: 1 + i as u64,
881                data: Bytes::copy_from_slice(&calldata),
882                gas_limit: 1_000_000,
883                ..Default::default()
884            };
885            let result = evm.transact_commit(tx).unwrap();
886            match result {
887                ExecutionResult::Success { output, .. } => {
888                    let data = match &output {
889                        Output::Call(bytes) => bytes,
890                        other => panic!("expected Call output, got: {other:?}"),
891                    };
892                    let addr = Address::from_slice(&data[12..32]);
893                    assert_ne!(addr, Address::ZERO, "CREATE2 should return a non-zero address");
894                    deployed_addrs.push(addr);
895                }
896                other => panic!("expected Success, got: {other:?}"),
897            }
898        }
899
900        // All deployed addresses should be different (different salts).
901        deployed_addrs.sort();
902        deployed_addrs.dedup();
903        assert_eq!(
904            deployed_addrs.len(),
905            3,
906            "CREATE2 with different salts should yield different addresses"
907        );
908
909        // Call each deployed contract — all should return 0xAB.
910        for (i, &addr) in deployed_addrs.iter().enumerate() {
911            let tx = TxEnv {
912                kind: TxKind::Call(addr),
913                nonce: 4 + i as u64,
914                gas_limit: 1_000_000,
915                ..Default::default()
916            };
917            let result = evm.transact_commit(tx).unwrap();
918            match result {
919                ExecutionResult::Success { output, .. } => {
920                    let data = match &output {
921                        Output::Call(bytes) => bytes,
922                        other => panic!("expected Call output, got: {other:?}"),
923                    };
924                    assert_eq!(U256::from_be_slice(data), U256::from(0xABu64));
925                }
926                other => panic!("expected Success, got: {other:?}"),
927            }
928        }
929
930        let stats = backend.stats();
931        assert!(stats.compilations_succeeded > 0, "expected compilations, got: {stats:?}");
932    }
933
934    /// CREATE deploys a contract whose runtime code gets JIT-compiled and called.
935    #[test]
936    fn jit_evm_create_and_call() {
937        let backend = blocking_backend();
938        let mut evm = test_jit_evm(backend);
939
940        // Runtime code: PUSH1 0x99 PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN
941        let runtime_code: &[u8] = &[0x60, 0x99, 0x5f, 0x52, 0x60, 0x20, 0x5f, 0xf3];
942
943        // Deploy via helper (uses CREATE internally).
944        let contract_addr = deploy_contract(&mut evm, runtime_code);
945
946        // Call the deployed contract.
947        let tx = TxEnv {
948            kind: TxKind::Call(contract_addr),
949            nonce: 1,
950            gas_limit: 1_000_000,
951            ..Default::default()
952        };
953        let result = evm.transact(tx).unwrap();
954        match result.result {
955            ExecutionResult::Success { output, .. } => {
956                let data = match &output {
957                    Output::Call(bytes) => bytes,
958                    other => panic!("expected Call output, got: {other:?}"),
959                };
960                assert_eq!(U256::from_be_slice(data), U256::from(0x99u64));
961            }
962            other => panic!("expected Success, got: {other:?}"),
963        }
964    }
965}