arb_evm/
evm.rs

1use core::fmt::Debug;
2
3use alloy_evm::{
4    Database, Evm, EvmEnv, EvmFactory, eth::EthEvmContext, precompiles::PrecompilesMap,
5};
6use alloy_primitives::{Address, B256, Bytes, U256};
7use arb_precompiles::register_arb_precompiles;
8use arb_stylus::{
9    StylusEvmApi, config::StylusConfig, ink::Gas as StylusGas, meter::MeteredMachine,
10    run::RunProgram,
11};
12use arbos::programs::types::EvmData;
13use revm::{
14    ExecuteEvm, InspectEvm, Inspector, SystemCallEvm,
15    context::{
16        ContextSetters, Evm as RevmEvm, FrameStack,
17        result::{EVMError, ExecutionResult, InvalidTransaction},
18    },
19    context_interface::{
20        ContextTr, JournalTr,
21        host::LoadError,
22        result::{HaltReason, ResultAndState},
23    },
24    handler::{
25        EthFrame, EvmTr, FrameResult, Handler, ItemOrResult, MainnetHandler, PrecompileProvider,
26        instructions::EthInstructions,
27    },
28    inspector::{InspectorHandler, NoOpInspector},
29    interpreter::{
30        CallInput, CallInputs, CallOutcome, CallScheme, FrameInput, Gas as EvmGas, Host,
31        InstructionContext, InstructionResult, InterpreterResult, InterpreterTypes,
32        interpreter::EthInterpreter,
33        interpreter_action::FrameInit,
34        interpreter_types::{InputsTr, ReturnData, RuntimeFlag, StackTr},
35    },
36    primitives::hardfork::SpecId,
37};
38
39use crate::transaction::ArbTransaction;
40
41/// BLOBBASEFEE opcode (0x4a).
42const BLOBBASEFEE_OPCODE: u8 = 0x4a;
43
44/// SELFDESTRUCT opcode (0xff).
45const SELFDESTRUCT_OPCODE: u8 = 0xff;
46
47/// NUMBER opcode (0x43).
48const NUMBER_OPCODE: u8 = 0x43;
49
50/// BLOCKHASH opcode (0x40).
51const BLOCKHASH_OPCODE: u8 = 0x40;
52
53/// BALANCE opcode (0x31).
54const BALANCE_OPCODE: u8 = 0x31;
55
56/// Arbitrum NUMBER: returns the L1 block number recorded during StartBlock.
57///
58/// `block_env.number` is configured to hold the L1 block number for Arbitrum
59/// EVM execution; reading the host preserves consensus semantics without
60/// touching any per-thread global.
61fn arb_number<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
62    let l1_block = ctx.host.block_number();
63    if !ctx.interpreter.stack.push(l1_block) {
64        ctx.interpreter.halt(InstructionResult::StackOverflow);
65    }
66}
67
68/// Arbitrum BLOCKHASH: uses L1 block number for range check.
69///
70/// Standard BLOCKHASH compares the requested block number against block_env.number,
71/// which is the L2 block number. Since Arbitrum's NUMBER opcode returns the L1
72/// block number, BLOCKHASH must also use L1 block numbers for the range check.
73/// Otherwise requests for L1 block hashes would always be out of range.
74fn arb_blockhash<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
75    use revm::interpreter::InstructionResult;
76
77    let requested = match ctx.interpreter.stack.pop() {
78        Some(v) => v,
79        None => {
80            ctx.interpreter.halt(InstructionResult::StackUnderflow);
81            return;
82        }
83    };
84
85    let l1_block_number = ctx.host.block_number();
86
87    let Some(diff) = l1_block_number.checked_sub(requested) else {
88        if !ctx.interpreter.stack.push(U256::ZERO) {
89            ctx.interpreter.halt(InstructionResult::StackOverflow);
90        }
91        return;
92    };
93
94    let diff_u64: u64 = diff.try_into().unwrap_or(u64::MAX);
95    if diff_u64 == 0 || diff_u64 > 256 {
96        if !ctx.interpreter.stack.push(U256::ZERO) {
97            ctx.interpreter.halt(InstructionResult::StackOverflow);
98        }
99        return;
100    }
101
102    let requested_u64: u64 = requested.try_into().unwrap_or(u64::MAX);
103    match ctx.host.block_hash(requested_u64) {
104        Some(hash) => {
105            if !ctx.interpreter.stack.push(U256::from_be_bytes(hash.0)) {
106                ctx.interpreter.halt(InstructionResult::StackOverflow);
107            }
108        }
109        None => {
110            ctx.interpreter.halt_fatal();
111        }
112    }
113}
114
115// SHA3 tracer removed — can't easily wrap standard handler
116
117/// Arbitrum BALANCE: subtracts the poster-fee correction when a contract
118/// reads the transaction sender's balance, so that the observed value matches
119/// a full-`gas_limit * basefee` buy-gas charge.
120fn arb_balance<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
121    let addr_u256 = match ctx.interpreter.stack.pop() {
122        Some(v) => v,
123        None => {
124            ctx.interpreter
125                .halt(revm::interpreter::InstructionResult::StackUnderflow);
126            return;
127        }
128    };
129
130    let addr = alloy_primitives::Address::from_word(alloy_primitives::B256::from(
131        addr_u256.to_be_bytes::<32>(),
132    ));
133
134    let sender = ctx.host.caller();
135    let correction = poster_balance_correction_word();
136
137    let spec_id = ctx.interpreter.runtime_flag.spec_id();
138    if spec_id.is_enabled_in(revm::primitives::hardfork::SpecId::BERLIN) {
139        let Some(state_load) = ctx.host.balance(addr) else {
140            ctx.interpreter.halt_fatal();
141            return;
142        };
143        let gas_cost = if state_load.is_cold { 2600u64 } else { 100u64 };
144        if !ctx.interpreter.gas.record_cost(gas_cost) {
145            ctx.interpreter
146                .halt(revm::interpreter::InstructionResult::OutOfGas);
147            return;
148        }
149
150        let balance = if addr == sender {
151            state_load.data.saturating_sub(correction)
152        } else {
153            state_load.data
154        };
155
156        if !ctx.interpreter.stack.push(balance) {
157            ctx.interpreter
158                .halt(revm::interpreter::InstructionResult::StackOverflow);
159        }
160    } else {
161        let Some(state_load) = ctx.host.balance(addr) else {
162            ctx.interpreter.halt_fatal();
163            return;
164        };
165
166        let balance = if addr == sender {
167            state_load.data.saturating_sub(correction)
168        } else {
169            state_load.data
170        };
171
172        if !ctx.interpreter.stack.push(balance) {
173            ctx.interpreter
174                .halt(revm::interpreter::InstructionResult::StackOverflow);
175        }
176    }
177}
178
179/// SELFBALANCE opcode (0x47).
180const SELFBALANCE_OPCODE: u8 = 0x47;
181
182/// Arbitrum SELFBALANCE: adjusts for poster fee correction if the executing
183/// contract IS the tx sender (edge case: sender calls own address).
184fn arb_selfbalance<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
185    let target = ctx.interpreter.input.target_address();
186
187    let Some(state_load) = ctx.host.balance(target) else {
188        ctx.interpreter.halt_fatal();
189        return;
190    };
191
192    let sender = ctx.host.caller();
193    let balance = if target == sender {
194        state_load
195            .data
196            .saturating_sub(poster_balance_correction_word())
197    } else {
198        state_load.data
199    };
200
201    if !ctx.interpreter.stack.push(balance) {
202        ctx.interpreter
203            .halt(revm::interpreter::InstructionResult::StackOverflow);
204    }
205}
206
207// EVM opcodes are wired into revm via a `[Instruction; 256]` table built from
208// bare `fn` pointers, so the `arb_balance` / `arb_selfbalance` overrides cannot
209// accept an extra ctx parameter. The executor publishes the active tx's
210// `poster_balance_correction` into this thread-local at the same point it
211// stages the precompile ctx; opcode reads stay zero-cost and consensus-only.
212//
213// Scope: arb-evm only — `arb-precompiles` is forbidden from holding any
214// `thread_local!` by the lint job in `.github/workflows/lint.yml`.
215thread_local! {
216    static POSTER_BALANCE_CORRECTION: std::cell::Cell<u128> = const { std::cell::Cell::new(0) };
217}
218
219/// Publish the poster-balance correction word for the active tx so that
220/// `arb_balance` / `arb_selfbalance` opcode reads observe it.
221pub fn set_poster_balance_correction(value: u128) {
222    POSTER_BALANCE_CORRECTION.with(|cell| cell.set(value));
223}
224
225/// Reset the per-tx poster-balance correction to zero.
226pub fn clear_poster_balance_correction() {
227    POSTER_BALANCE_CORRECTION.with(|cell| cell.set(0));
228}
229
230fn poster_balance_correction_word() -> U256 {
231    POSTER_BALANCE_CORRECTION.with(|cell| U256::from(cell.get()))
232}
233
234/// BLOBBASEFEE is not supported on Arbitrum — execution halts.
235fn arb_blob_basefee<WIRE: InterpreterTypes, H: Host + ?Sized>(
236    ctx: InstructionContext<'_, H, WIRE>,
237) {
238    ctx.interpreter.halt(InstructionResult::OpcodeNotFound);
239}
240
241/// Arbitrum SELFDESTRUCT: reverts if the acting account is a Stylus program,
242/// otherwise delegates to the standard EIP-6780 selfdestruct logic.
243fn arb_selfdestruct<WIRE: InterpreterTypes, H: Host + ?Sized>(
244    ctx: InstructionContext<'_, H, WIRE>,
245) {
246    if ctx.interpreter.runtime_flag.is_static() {
247        ctx.interpreter
248            .halt(InstructionResult::StateChangeDuringStaticCall);
249        return;
250    }
251
252    // Stylus programs cannot be self-destructed.
253    let acting_addr = ctx.interpreter.input.target_address();
254    match ctx.host.load_account_code(acting_addr) {
255        Some(code_load) => {
256            if arb_stylus::is_stylus_runnable(&code_load.data) {
257                ctx.interpreter.halt(InstructionResult::Revert);
258                return;
259            }
260        }
261        None => {
262            ctx.interpreter.halt_fatal();
263            return;
264        }
265    }
266
267    // Standard selfdestruct logic (matching revm's EIP-6780 implementation).
268    // Pop U256 and convert to Address manually (avoids pop_address() which
269    // triggers a ruint 1.17 const eval panic due to U256->Address byte size mismatch).
270    let Some(raw) = ctx.interpreter.stack.pop() else {
271        ctx.interpreter.halt(InstructionResult::StackUnderflow);
272        return;
273    };
274    let target = Address::from_word(alloy_primitives::B256::from(raw.to_be_bytes()));
275
276    let spec = ctx.interpreter.runtime_flag.spec_id();
277    let cold_load_gas = ctx.host.gas_params().selfdestruct_cold_cost();
278    let skip_cold_load = ctx.interpreter.gas.remaining() < cold_load_gas;
279
280    let res = match ctx.host.selfdestruct(acting_addr, target, skip_cold_load) {
281        Ok(res) => res,
282        Err(LoadError::ColdLoadSkipped) => {
283            ctx.interpreter.halt_oog();
284            return;
285        }
286        Err(LoadError::DBError) => {
287            ctx.interpreter.halt_fatal();
288            return;
289        }
290    };
291
292    // EIP-161: State trie clearing.
293    let should_charge_topup = if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
294        res.had_value && !res.target_exists
295    } else {
296        !res.target_exists
297    };
298
299    let gas_cost = ctx
300        .host
301        .gas_params()
302        .selfdestruct_cost(should_charge_topup, res.is_cold);
303    if !ctx.interpreter.gas.record_cost(gas_cost) {
304        ctx.interpreter.halt_oog();
305        return;
306    }
307
308    if !res.previously_destroyed {
309        ctx.interpreter
310            .gas
311            .record_refund(ctx.host.gas_params().selfdestruct_refund());
312    }
313
314    ctx.interpreter.halt(InstructionResult::SelfDestruct);
315}
316
317/// Reset per-tx Stylus state at the start of every transaction.
318pub fn reset_stylus_pages(ctx: &arb_context::ArbPrecompileCtx) {
319    let mut tx = ctx.tx.lock();
320    tx.stylus_program_counts.clear();
321}
322
323// ── Stylus storage helpers ───────────────────────────────────────────
324
325use arb_storage::{
326    ARBOS_STATE_ADDRESS, DatabaseError, DatabaseErrorInfo, Detached, Storage, StorageBackend,
327    StorageError, SystemStateBackend,
328    layout::{
329        PROGRAMS_SUBSPACE, ROOT_STORAGE_KEY, derive_subspace_key, map_slot_b256,
330        programs::{MODULE_HASHES_KEY, PARAMS_KEY, PROGRAM_DATA_KEY},
331    },
332};
333use arbos::programs::{Program, memory::MemoryModel, params::StylusParams};
334
335/// Read a storage slot from ArbOS state via the journal.
336fn sload_arbos<DB: Database>(journal: &mut revm::Journal<DB>, slot: U256) -> Option<U256> {
337    let _ = journal
338        .inner
339        .load_account(&mut journal.database, ARBOS_STATE_ADDRESS)
340        .ok()?;
341    let result = journal
342        .inner
343        .sload(&mut journal.database, ARBOS_STATE_ADDRESS, slot, false)
344        .ok()?;
345    Some(result.data)
346}
347
348/// `StorageBackend` adapter over a live `revm::Journal`, used so dispatch can
349/// drive the canonical `arb-storage` accessors without an `EvmInternals` in
350/// scope. Reads route through the journal's cold/warm cache; writes are
351/// unused on the dispatch path but routed identically for completeness.
352struct JournalBackend<'a, DB: Database> {
353    journal: &'a mut revm::Journal<DB>,
354}
355
356impl<'a, DB: Database> JournalBackend<'a, DB> {
357    fn new(journal: &'a mut revm::Journal<DB>) -> Self {
358        Self { journal }
359    }
360}
361
362impl<DB: Database> SystemStateBackend for JournalBackend<'_, DB> {
363    type Error = StorageError;
364
365    fn sload_system(&mut self, account: Address, slot: U256) -> Result<U256, Self::Error> {
366        // Route through the journal so in-flight writes within the current
367        // call are observed; the perf win comes from the per-block
368        // ArbosState cache, not from bypassing the journal here.
369        let journal = &mut *self.journal;
370        journal
371            .inner
372            .load_account(&mut journal.database, account)
373            .map_err(|e| {
374                StorageError::Database(DatabaseError::Read(DatabaseErrorInfo::new(format!(
375                    "{e:?}"
376                ))))
377            })?;
378        let value = journal
379            .inner
380            .sload(&mut journal.database, account, slot, false)
381            .map_err(|e| {
382                StorageError::Database(DatabaseError::Read(DatabaseErrorInfo::new(format!(
383                    "{e:?}"
384                ))))
385            })?;
386        Ok(value.data)
387    }
388}
389
390impl<DB: Database> StorageBackend for JournalBackend<'_, DB> {
391    fn sload(&mut self, account: Address, slot: U256) -> Result<U256, StorageError> {
392        let journal = &mut *self.journal;
393        journal
394            .inner
395            .load_account(&mut journal.database, account)
396            .map_err(|e| {
397                StorageError::Database(DatabaseError::Read(DatabaseErrorInfo::new(format!(
398                    "{e:?}"
399                ))))
400            })?;
401        let value = journal
402            .inner
403            .sload(&mut journal.database, account, slot, false)
404            .map_err(|e| {
405                StorageError::Database(DatabaseError::Read(DatabaseErrorInfo::new(format!(
406                    "{e:?}"
407                ))))
408            })?;
409        Ok(value.data)
410    }
411
412    fn sstore(&mut self, account: Address, slot: U256, value: U256) -> Result<(), StorageError> {
413        let journal = &mut *self.journal;
414        journal
415            .inner
416            .sstore(&mut journal.database, account, slot, value, false)
417            .map_err(|e| {
418                StorageError::Database(DatabaseError::Write(DatabaseErrorInfo::new(format!(
419                    "{e:?}"
420                ))))
421            })?;
422        Ok(())
423    }
424}
425
426/// Detached `Storage` handle for the `programs/params` subspace.
427///
428/// Reads route through a [`StorageBackend`]; no executor state pointer is
429/// required.
430fn programs_params_storage() -> Storage<'static, Detached> {
431    let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
432    let params_key = derive_subspace_key(programs_key.as_slice(), PARAMS_KEY);
433    Storage::detached(ARBOS_STATE_ADDRESS, params_key)
434}
435
436/// Read program data word by code hash.
437fn read_program_word<DB: Database>(
438    journal: &mut revm::Journal<DB>,
439    code_hash: B256,
440) -> Option<B256> {
441    let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
442    let data_key = derive_subspace_key(programs_key.as_slice(), PROGRAM_DATA_KEY);
443    let slot = map_slot_b256(data_key.as_slice(), &code_hash);
444    sload_arbos(journal, slot).map(|v| B256::from(v.to_be_bytes::<32>()))
445}
446
447/// Read the activation-time module hash for a Stylus program by code hash.
448/// Read the activation-time module hash stored at programs subspace key [2].
449fn read_module_hash<DB: Database>(
450    journal: &mut revm::Journal<DB>,
451    code_hash: B256,
452) -> Option<B256> {
453    let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
454    let module_hashes_key = derive_subspace_key(programs_key.as_slice(), MODULE_HASHES_KEY);
455    let slot = map_slot_b256(module_hashes_key.as_slice(), &code_hash);
456    sload_arbos(journal, slot).map(|v| B256::from(v.to_be_bytes::<32>()))
457}
458
459/// Compute upfront gas cost for a Stylus call, per `Programs.CallProgram`.
460fn stylus_call_gas_cost(
461    params: &StylusParams,
462    program: &Program,
463    pages_open: u16,
464    pages_ever: u16,
465    arbos_version: u64,
466) -> u64 {
467    let model = MemoryModel::new(params.free_pages, params.page_gas);
468    let mut cost = model.gas_cost(program.footprint, pages_open, pages_ever);
469
470    let cached = program.cached;
471    if cached || program.version > 1 {
472        cost = cost.saturating_add(program.cached_gas(params));
473    }
474    if !cached {
475        cost = cost.saturating_add(program.init_gas(params));
476    }
477    let new_open = pages_open.saturating_add(program.footprint);
478    if arb_stylus::env::page_limit_exceeded(arbos_version, params.page_limit, new_open) {
479        cost = cost.saturating_add(u64::MAX);
480    }
481    cost
482}
483
484// ── Stylus sub-call trampolines ─────────────────────────────────────
485
486use arb_stylus::evm_api_impl::{SubCallResult, SubCreateResult};
487
488/// Read the current (pages_open, pages_ever) carried on the active `TxCtx`.
489fn read_tx_pages(ctx: &arb_context::ArbPrecompileCtx) -> (u16, u16) {
490    let tx = ctx.tx.lock();
491    (tx.stylus_pages_open, tx.stylus_pages_ever)
492}
493
494/// Write `pages_open` and `pages_ever` to the active `TxCtx`.
495fn write_tx_pages(ctx: &arb_context::ArbPrecompileCtx, pages: (u16, u16)) {
496    let mut tx = ctx.tx.lock();
497    tx.stylus_pages_open = pages.0;
498    tx.stylus_pages_ever = pages.1;
499}
500
501/// Monomorphized trampoline for Stylus sub-calls (CALL/DELEGATECALL/STATICCALL).
502#[allow(clippy::too_many_arguments)]
503fn stylus_call_trampoline<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
504    ctx: *mut (),
505    precompile_ctx: *const (),
506    call_type: u8,
507    contract: Address,
508    caller: Address,
509    storage_addr: Address,
510    input: &[u8],
511    gas: u64,
512    value: U256,
513    parent_pages: (u16, u16),
514) -> SubCallResult
515where
516    BlockEnv: revm::context::Block,
517    TxEnv: revm::context::Transaction,
518    CfgEnv: revm::context::Cfg,
519    DB: Database,
520{
521    // SAFETY: `ctx` and `precompile_ctx` are the type-erased pointers stored
522    // by `execute_stylus_program` when it built the `StylusEvmApi` for this
523    // call frame. The concrete `revm::Context` and `ArbPrecompileCtx` types
524    // monomorphise this trampoline, so the cast is the structural inverse
525    // of the type-erasure performed when the host function was installed.
526    // Both pointers are valid for the duration of the parent EVM frame,
527    // which is the only frame that can invoke this trampoline.
528    let context = unsafe {
529        &mut *(ctx as *mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>)
530    };
531    let pre_ctx = unsafe { &*(precompile_ctx as *const arb_context::ArbPrecompileCtx) };
532
533    write_tx_pages(pre_ctx, parent_pages);
534
535    let is_static = call_type == 2;
536    let is_delegate = call_type == 1;
537    let is_callcode = call_type == 3;
538
539    struct ActorGuard<'a> {
540        ctx: &'a arb_context::ArbPrecompileCtx,
541        addr: Address,
542        pushed: bool,
543    }
544    impl Drop for ActorGuard<'_> {
545        fn drop(&mut self) {
546            if self.pushed {
547                self.ctx.pop_stylus_program(self.addr);
548            }
549        }
550    }
551    let _actor_guard = if !is_delegate && !is_callcode {
552        pre_ctx.push_stylus_program(storage_addr);
553        ActorGuard {
554            ctx: pre_ctx,
555            addr: storage_addr,
556            pushed: true,
557        }
558    } else {
559        ActorGuard {
560            ctx: pre_ctx,
561            addr: storage_addr,
562            pushed: false,
563        }
564    };
565
566    let checkpoint = context.journaled_state.inner.checkpoint();
567
568    if !is_delegate && !value.is_zero() {
569        let transfer_result = context.journaled_state.inner.transfer(
570            &mut context.journaled_state.database,
571            caller,
572            contract,
573            value,
574        );
575        if matches!(transfer_result, Err(_) | Ok(Some(_))) {
576            context.journaled_state.inner.checkpoint_revert(checkpoint);
577            return SubCallResult {
578                output: Vec::new(),
579                gas_cost: 0,
580                success: false,
581                refund: 0,
582                pages: read_tx_pages(pre_ctx),
583            };
584        }
585    }
586
587    let code_address = contract;
588    let target_address = storage_addr;
589    let _ = is_delegate;
590
591    let call_scheme = match call_type {
592        0 => CallScheme::Call,
593        1 => CallScheme::DelegateCall,
594        2 => CallScheme::StaticCall,
595        _ => CallScheme::Call,
596    };
597
598    let call_value = if is_delegate {
599        revm::interpreter::CallValue::Apparent(value)
600    } else {
601        revm::interpreter::CallValue::Transfer(value)
602    };
603
604    let sub_inputs = CallInputs {
605        input: CallInput::Bytes(input.to_vec().into()),
606        gas_limit: gas,
607        target_address,
608        bytecode_address: code_address,
609        caller,
610        value: call_value,
611        scheme: call_scheme,
612        is_static,
613        return_memory_offset: 0..0,
614        known_bytecode: None,
615    };
616
617    {
618        let spec: revm::primitives::hardfork::SpecId = context.cfg.spec().into();
619        let mut precompiles =
620            alloy_evm::precompiles::PrecompilesMap::from(revm::handler::EthPrecompiles::new(spec));
621        // SAFETY: `precompile_ctx` was obtained as `Arc::as_ptr` from a
622        // live `Arc<ArbPrecompileCtx>` held by the dispatch path that
623        // installed this trampoline. `increment_strong_count` keeps the
624        // parent's refcount intact while `from_raw` rebuilds a clonable
625        // handle whose Drop will balance the increment. The pointer is
626        // valid for the duration of the parent EVM frame.
627        let pre_arc = unsafe {
628            let raw = precompile_ctx as *const arb_context::ArbPrecompileCtx;
629            std::sync::Arc::increment_strong_count(raw);
630            std::sync::Arc::from_raw(raw)
631        };
632        register_arb_precompiles(&mut precompiles, pre_arc.clone());
633        let mut arb_map = ArbPrecompilesMap::new(precompiles, pre_arc);
634        let dispatch_result = <ArbPrecompilesMap as PrecompileProvider<
635            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
636        >>::run(&mut arb_map, context, &sub_inputs);
637
638        match dispatch_result {
639            Ok(Some(result)) => {
640                let success = result.result.is_ok();
641                let output = result.output.to_vec();
642                // On failure (revert, OOG, error) a non-reverting callee has
643                // returned gas left in result.gas, but a precompile that
644                // signalled OOG/PrecompileError leaves `result.gas` untouched
645                // (see alloy-evm precompile runner). Ethereum semantics charge
646                // the caller for all gas forwarded on non-revert failures.
647                let gas_used = if success || matches!(result.result, InstructionResult::Revert) {
648                    gas.saturating_sub(result.gas.remaining())
649                } else {
650                    gas
651                };
652                let refund = if success { result.gas.refunded() } else { 0 };
653                if success {
654                    context.journaled_state.inner.checkpoint_commit();
655                } else {
656                    context.journaled_state.inner.checkpoint_revert(checkpoint);
657                }
658                return SubCallResult {
659                    output,
660                    gas_cost: gas_used,
661                    success,
662                    refund,
663                    pages: read_tx_pages(pre_ctx),
664                };
665            }
666            Ok(None) => {}
667            Err(_) => {
668                context.journaled_state.inner.checkpoint_revert(checkpoint);
669                return SubCallResult {
670                    output: Vec::new(),
671                    gas_cost: gas,
672                    success: false,
673                    refund: 0,
674                    pages: read_tx_pages(pre_ctx),
675                };
676            }
677        }
678    }
679
680    let bytecode = match context
681        .journaled_state
682        .inner
683        .load_code(&mut context.journaled_state.database, code_address)
684    {
685        Ok(acc) => acc
686            .data
687            .info
688            .code
689            .as_ref()
690            .map(|c| c.original_bytes())
691            .unwrap_or_default(),
692        Err(_) => {
693            context.journaled_state.inner.checkpoint_revert(checkpoint);
694            return SubCallResult {
695                output: Vec::new(),
696                gas_cost: 0,
697                success: false,
698                refund: 0,
699                pages: read_tx_pages(pre_ctx),
700            };
701        }
702    };
703
704    // EIP-7702: if the callee carries a delegation designator
705    // `0xef 0x01 0x00 || delegate_address`, load the delegate's code and use
706    // that for dispatch. Without this the 23-byte stub is fed directly to the
707    // EVM interpreter; `0xef` has no defined opcode (EIP-3541 is disabled
708    // for Stylus) so the sub-call traps with all forwarded gas consumed.
709    let (bytecode, bytecode_addr) = if bytecode.len() == 23
710        && bytecode[0] == 0xef
711        && bytecode[1] == 0x01
712        && bytecode[2] == 0x00
713    {
714        let delegate = Address::from_slice(&bytecode[3..23]);
715        match context
716            .journaled_state
717            .inner
718            .load_code(&mut context.journaled_state.database, delegate)
719        {
720            Ok(acc) => {
721                let code = acc
722                    .data
723                    .info
724                    .code
725                    .as_ref()
726                    .map(|c| c.original_bytes())
727                    .unwrap_or_default();
728                (code, delegate)
729            }
730            Err(_) => {
731                context.journaled_state.inner.checkpoint_revert(checkpoint);
732                return SubCallResult {
733                    output: Vec::new(),
734                    gas_cost: 0,
735                    success: false,
736                    refund: 0,
737                    pages: read_tx_pages(pre_ctx),
738                };
739            }
740        }
741    } else {
742        (bytecode, code_address)
743    };
744
745    if bytecode.is_empty() {
746        context.journaled_state.inner.checkpoint_commit();
747        return SubCallResult {
748            output: Vec::new(),
749            gas_cost: 0,
750            success: true,
751            refund: 0,
752            pages: read_tx_pages(pre_ctx),
753        };
754    }
755
756    let sub_inputs = CallInputs {
757        bytecode_address: bytecode_addr,
758        ..sub_inputs
759    };
760
761    if pre_ctx.block.arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS
762        && arb_stylus::is_stylus_runnable(&bytecode)
763    {
764        // SAFETY: see the `Arc::increment_strong_count` site above —
765        // `precompile_ctx` was produced by `Arc::as_ptr` and is valid
766        // for this frame's duration. The increment is balanced by the
767        // returned `Arc`'s Drop.
768        let arc = unsafe {
769            let raw = precompile_ctx as *const arb_context::ArbPrecompileCtx;
770            std::sync::Arc::increment_strong_count(raw);
771            std::sync::Arc::from_raw(raw)
772        };
773        let result = execute_stylus_program(context, &sub_inputs, &bytecode, &arc);
774        let pages = read_tx_pages(pre_ctx);
775        let success = result.result.is_ok();
776        let output = result.output.to_vec();
777        let gas_used = gas.saturating_sub(result.gas.remaining());
778        let refund = result.gas.refunded();
779        if success {
780            context.journaled_state.inner.checkpoint_commit();
781        } else {
782            context.journaled_state.inner.checkpoint_revert(checkpoint);
783        }
784        return SubCallResult {
785            output,
786            gas_cost: gas_used,
787            success,
788            refund,
789            pages,
790        };
791    }
792
793    let result = run_evm_bytecode(context, &sub_inputs, &bytecode, gas, pre_ctx);
794    let pages = read_tx_pages(pre_ctx);
795    let success = result.result.is_ok();
796    let output = result.output.to_vec();
797    let gas_used = if success || matches!(result.result, InstructionResult::Revert) {
798        gas.saturating_sub(result.gas.remaining())
799    } else {
800        gas
801    };
802    let refund = result.gas.refunded();
803    if success {
804        context.journaled_state.inner.checkpoint_commit();
805    } else {
806        context.journaled_state.inner.checkpoint_revert(checkpoint);
807    }
808    SubCallResult {
809        output,
810        gas_cost: gas_used,
811        success,
812        refund,
813        pages,
814    }
815}
816
817/// Monomorphized trampoline for Stylus CREATE/CREATE2 operations.
818#[allow(clippy::too_many_arguments)]
819fn stylus_create_trampoline<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
820    ctx: *mut (),
821    precompile_ctx: *const (),
822    caller: Address,
823    code: &[u8],
824    gas: u64,
825    endowment: U256,
826    salt: Option<B256>,
827    parent_pages: (u16, u16),
828) -> SubCreateResult
829where
830    BlockEnv: revm::context::Block,
831    TxEnv: revm::context::Transaction,
832    CfgEnv: revm::context::Cfg,
833    DB: Database,
834{
835    // SAFETY: see `stylus_call_trampoline` for the type-erasure invariant —
836    // `ctx` and `precompile_ctx` were installed by `execute_stylus_program`
837    // for this monomorphised trampoline; both are valid for the lifetime of
838    // the parent EVM frame that invokes the host function.
839    let pre_ctx = unsafe { &*(precompile_ctx as *const arb_context::ArbPrecompileCtx) };
840    {
841        let mut tx = pre_ctx.tx.lock();
842        tx.stylus_pages_open = parent_pages.0;
843        tx.stylus_pages_ever = parent_pages.1;
844    }
845    let context = unsafe {
846        &mut *(ctx as *mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>)
847    };
848
849    let (caller_nonce, caller_balance) = {
850        let acc = context
851            .journaled_state
852            .inner
853            .load_account(&mut context.journaled_state.database, caller);
854        acc.map(|a| (a.data.info.nonce, a.data.info.balance))
855            .unwrap_or((0, U256::ZERO))
856    };
857
858    if caller_balance < endowment {
859        return SubCreateResult {
860            address: None,
861            output: Vec::new(),
862            gas_cost: 0,
863            pages: read_tx_pages(pre_ctx),
864        };
865    }
866
867    let created_address = if let Some(salt) = salt {
868        let code_hash = alloy_primitives::keccak256(code);
869        let mut buf = Vec::with_capacity(1 + 20 + 32 + 32);
870        buf.push(0xff);
871        buf.extend_from_slice(caller.as_slice());
872        buf.extend_from_slice(salt.as_slice());
873        buf.extend_from_slice(code_hash.as_slice());
874        Address::from_slice(&alloy_primitives::keccak256(&buf)[12..])
875    } else {
876        use alloy_rlp::Encodable;
877        let mut rlp_buf = Vec::with_capacity(64);
878        alloy_rlp::Header {
879            list: true,
880            payload_length: caller.length() + caller_nonce.length(),
881        }
882        .encode(&mut rlp_buf);
883        caller.encode(&mut rlp_buf);
884        caller_nonce.encode(&mut rlp_buf);
885        Address::from_slice(&alloy_primitives::keccak256(&rlp_buf)[12..])
886    };
887
888    struct CreateActorGuard<'a> {
889        ctx: &'a arb_context::ArbPrecompileCtx,
890        addr: Address,
891    }
892    impl Drop for CreateActorGuard<'_> {
893        fn drop(&mut self) {
894            self.ctx.pop_stylus_program(self.addr);
895        }
896    }
897    pre_ctx.push_stylus_program(created_address);
898    let _create_guard = CreateActorGuard {
899        ctx: pre_ctx,
900        addr: created_address,
901    };
902
903    {
904        use revm::context_interface::journaled_state::account::JournaledAccountTr;
905        let bumped = match context
906            .journaled_state
907            .inner
908            .load_account_mut(&mut context.journaled_state.database, caller)
909        {
910            Ok(mut caller_acc) => caller_acc.data.bump_nonce(),
911            Err(_) => false,
912        };
913        if !bumped {
914            return SubCreateResult {
915                address: None,
916                output: Vec::new(),
917                gas_cost: gas,
918                pages: read_tx_pages(pre_ctx),
919            };
920        }
921    }
922
923    if context
924        .journaled_state
925        .inner
926        .load_account(&mut context.journaled_state.database, created_address)
927        .is_err()
928    {
929        return SubCreateResult {
930            address: None,
931            output: Vec::new(),
932            gas_cost: gas,
933            pages: read_tx_pages(pre_ctx),
934        };
935    }
936
937    let spec: revm::primitives::hardfork::SpecId = context.cfg.spec().into();
938    let checkpoint = match context.journaled_state.inner.create_account_checkpoint(
939        caller,
940        created_address,
941        endowment,
942        spec,
943    ) {
944        Ok(cp) => cp,
945        Err(_) => {
946            return SubCreateResult {
947                address: None,
948                output: Vec::new(),
949                gas_cost: gas,
950                pages: read_tx_pages(pre_ctx),
951            };
952        }
953    };
954
955    let init_inputs = CallInputs {
956        input: CallInput::Bytes(Bytes::new()),
957        gas_limit: gas,
958        target_address: created_address,
959        bytecode_address: created_address,
960        caller,
961        value: revm::interpreter::CallValue::Transfer(endowment),
962        scheme: CallScheme::Call,
963        is_static: false,
964        return_memory_offset: 0..0,
965        known_bytecode: None,
966    };
967
968    let result = run_evm_bytecode(context, &init_inputs, code, gas, pre_ctx);
969    let pages = read_tx_pages(pre_ctx);
970    let success = result.result.is_ok();
971
972    if success {
973        let deployed_code = result.output.to_vec();
974        let max_code_size = context.cfg.max_code_size();
975        if deployed_code.len() > max_code_size {
976            context.journaled_state.inner.checkpoint_revert(checkpoint);
977            return SubCreateResult {
978                address: None,
979                output: Vec::new(),
980                gas_cost: gas,
981                pages,
982            };
983        }
984        let is_stylus =
985            arb_stylus::is_stylus_component(&deployed_code, pre_ctx.block.arbos_version);
986        if !deployed_code.is_empty() && deployed_code[0] == 0xEF && !is_stylus {
987            context.journaled_state.inner.checkpoint_revert(checkpoint);
988            return SubCreateResult {
989                address: None,
990                output: Vec::new(),
991                gas_cost: gas,
992                pages,
993            };
994        }
995        let deposit_cost = context
996            .cfg
997            .gas_params()
998            .code_deposit_cost(deployed_code.len());
999        let after_deposit_remaining = result.gas.remaining().saturating_sub(deposit_cost);
1000        if result.gas.remaining() < deposit_cost {
1001            context.journaled_state.inner.checkpoint_revert(checkpoint);
1002            return SubCreateResult {
1003                address: None,
1004                output: Vec::new(),
1005                gas_cost: gas,
1006                pages,
1007            };
1008        }
1009        let gas_used = gas.saturating_sub(after_deposit_remaining);
1010        let code_hash = alloy_primitives::keccak256(&deployed_code);
1011        let bytecode = revm::bytecode::Bytecode::new_raw(deployed_code.into());
1012        let _ = context
1013            .journaled_state
1014            .inner
1015            .load_account(&mut context.journaled_state.database, created_address);
1016        context
1017            .journaled_state
1018            .inner
1019            .set_code_with_hash(created_address, bytecode, code_hash);
1020        context.journaled_state.inner.checkpoint_commit();
1021        SubCreateResult {
1022            address: Some(created_address),
1023            output: Vec::new(),
1024            gas_cost: gas_used,
1025            pages,
1026        }
1027    } else {
1028        let output = result.output.to_vec();
1029        let gas_used = gas.saturating_sub(result.gas.remaining());
1030        context.journaled_state.inner.checkpoint_revert(checkpoint);
1031        SubCreateResult {
1032            address: None,
1033            output,
1034            gas_cost: gas_used,
1035            pages,
1036        }
1037    }
1038}
1039
1040/// Run EVM bytecode from a Stylus sub-call.
1041///
1042/// Creates an interpreter and runs in a loop, dispatching nested CALL/CREATE
1043/// actions through the Stylus call trampoline (which in turn uses
1044/// ArbPrecompilesMap for precompile/Stylus dispatch).
1045fn run_evm_bytecode<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
1046    context: &mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1047    inputs: &CallInputs,
1048    bytecode: &[u8],
1049    gas_limit: u64,
1050    pre_ctx: &arb_context::ArbPrecompileCtx,
1051) -> InterpreterResult
1052where
1053    BlockEnv: revm::context::Block,
1054    TxEnv: revm::context::Transaction,
1055    CfgEnv: revm::context::Cfg,
1056    DB: Database,
1057{
1058    use revm::{
1059        bytecode::Bytecode,
1060        interpreter::{
1061            FrameInput, InterpreterAction, SharedMemory,
1062            interpreter::{ExtBytecode, InputsImpl},
1063        },
1064    };
1065
1066    let code = Bytecode::new_raw(bytecode.to_vec().into());
1067    let ext_bytecode = ExtBytecode::new(code);
1068
1069    let call_value = inputs.value.get();
1070    let interp_input = InputsImpl {
1071        target_address: inputs.target_address,
1072        bytecode_address: Some(inputs.bytecode_address),
1073        caller_address: inputs.caller,
1074        input: inputs.input.clone(),
1075        call_value,
1076    };
1077
1078    let spec = context.cfg.spec();
1079
1080    let mut interpreter = revm::interpreter::Interpreter::new(
1081        SharedMemory::new(),
1082        ext_bytecode,
1083        interp_input,
1084        inputs.is_static,
1085        spec.clone().into(),
1086        gas_limit,
1087    );
1088
1089    // Install the Arbitrum opcode overrides on every sub-frame. The outer
1090    // factory does the same; this path is taken when a Stylus contract
1091    // re-enters EVM bytecode and must see identical semantics (notably NUMBER
1092    // returning the L1 block number that EIP-712 signatures depend on).
1093    type Ctx<B, T, C, D, Ch> = revm::Context<B, T, C, D, revm::Journal<D>, Ch>;
1094    let mut instructions = EthInstructions::<
1095        EthInterpreter,
1096        Ctx<BlockEnv, TxEnv, CfgEnv, DB, Chain>,
1097    >::new_mainnet_with_spec(spec.into());
1098    instructions.insert_instruction(
1099        BLOBBASEFEE_OPCODE,
1100        revm::interpreter::Instruction::new(arb_blob_basefee, 2),
1101    );
1102    instructions.insert_instruction(
1103        SELFDESTRUCT_OPCODE,
1104        revm::interpreter::Instruction::new(arb_selfdestruct, 5000),
1105    );
1106    instructions.insert_instruction(
1107        NUMBER_OPCODE,
1108        revm::interpreter::Instruction::new(arb_number, 2),
1109    );
1110    instructions.insert_instruction(
1111        BLOCKHASH_OPCODE,
1112        revm::interpreter::Instruction::new(arb_blockhash, 20),
1113    );
1114    instructions.insert_instruction(
1115        BALANCE_OPCODE,
1116        revm::interpreter::Instruction::new(arb_balance, 0),
1117    );
1118    instructions.insert_instruction(
1119        SELFBALANCE_OPCODE,
1120        revm::interpreter::Instruction::new(arb_selfbalance, 5),
1121    );
1122
1123    // Attribute this EVM sub-frame's opcodes by multi-gas dimension so a Stylus
1124    // contract calling Solidity prices the callee's storage/log gas like the EVM
1125    // would. Sub-calls dispatched below surface as `NewFrame`; their forwarded
1126    // gas is stripped via the inspector's call/create hooks and attributed by
1127    // the callee frame.
1128    let mut multi_gas_inspector = crate::multi_gas::MultiGasInspector::default();
1129    loop {
1130        let action = revm::inspector::inspect_instructions(
1131            context,
1132            &mut interpreter,
1133            &mut multi_gas_inspector,
1134            &instructions.instruction_table,
1135        );
1136
1137        match action {
1138            InterpreterAction::Return(result) => {
1139                pre_ctx.add_stylus_multi_gas(multi_gas_inspector.take_multi_gas());
1140                return result;
1141            }
1142            InterpreterAction::NewFrame(FrameInput::Call(mut sub_call)) => {
1143                revm::Inspector::call(&mut multi_gas_inspector, context, &mut sub_call);
1144                // Dispatch nested call through our trampoline.
1145                // For DELEGATECALL, target_address is the storage context (preserved
1146                // from parent), and caller is the msg.sender (preserved). For
1147                // CALL/STATICCALL, target_address == bytecode_address.
1148                //
1149                // Resolve `sub_call.input` against the *interpreter's* SharedMemory.
1150                // We can't use `sub_call.input.bytes(context)` because that reads
1151                // from `context.local()` — the global context's local memory —
1152                // whereas this frame's CALL/STATICCALL was issued against the
1153                // private SharedMemory we created at the top of this function.
1154                // Reading from the wrong memory returns garbage and silently
1155                // corrupts the input to ecrecover, transferFrom, etc.
1156                let resolved_input: Bytes = match &sub_call.input {
1157                    revm::interpreter::CallInput::Bytes(b) => b.clone(),
1158                    // A zero-length range carries no input; its start offset may
1159                    // sit past the un-grown memory, so skip the slice read.
1160                    revm::interpreter::CallInput::SharedBuffer(range) if range.is_empty() => {
1161                        Bytes::new()
1162                    }
1163                    revm::interpreter::CallInput::SharedBuffer(range) => {
1164                        // The range was computed by call_helpers as
1165                        //   range.start = relative_offset + local_memory_offset()
1166                        // i.e. an absolute offset into the SharedMemory buffer.
1167                        Bytes::from(
1168                            interpreter
1169                                .memory
1170                                .global_slice_range(range.clone())
1171                                .to_vec(),
1172                        )
1173                    }
1174                };
1175                let bytecode_address = sub_call.bytecode_address;
1176                let parent_pages = read_tx_pages(pre_ctx);
1177                let sub_result = stylus_call_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
1178                    context as *mut _ as *mut (),
1179                    pre_ctx as *const _ as *const (),
1180                    match sub_call.scheme {
1181                        CallScheme::Call | CallScheme::CallCode => 0,
1182                        CallScheme::DelegateCall => 1,
1183                        CallScheme::StaticCall => 2,
1184                    },
1185                    bytecode_address,
1186                    sub_call.caller,
1187                    sub_call.target_address,
1188                    &resolved_input,
1189                    sub_call.gas_limit,
1190                    sub_call.value.get(),
1191                    parent_pages,
1192                );
1193                write_tx_pages(pre_ctx, sub_result.pages);
1194
1195                let gas_remaining = sub_call.gas_limit.saturating_sub(sub_result.gas_cost);
1196                let ins_result = if sub_result.success {
1197                    InstructionResult::Return
1198                } else {
1199                    InstructionResult::Revert
1200                };
1201
1202                let output: Bytes = sub_result.output.into();
1203                let returned_len = output.len();
1204                let mem_start = sub_call.return_memory_offset.start;
1205                let mem_length = sub_call.return_memory_offset.len();
1206                let target_len = mem_length.min(returned_len);
1207
1208                interpreter.return_data.set_buffer(output);
1209
1210                let item = if ins_result.is_ok() {
1211                    U256::from(1)
1212                } else {
1213                    U256::ZERO
1214                };
1215                let _ = interpreter.stack.push(item);
1216
1217                if ins_result.is_ok_or_revert() {
1218                    interpreter.gas.erase_cost(gas_remaining);
1219                    if target_len > 0 {
1220                        interpreter
1221                            .memory
1222                            .set(mem_start, &interpreter.return_data.buffer()[..target_len]);
1223                    }
1224                }
1225
1226                if ins_result.is_ok() {
1227                    // Propagate the sub-call's SSTORE refund (EIP-3529) up
1228                    // through the Stylus -> EVM bytecode -> Stylus call chain.
1229                    // Mirrors revm's `EthFrame::return_result` which also does
1230                    // `interpreter.gas.record_refund(out_gas.refunded())` on
1231                    // success. Without this, a Stylus contract that goes
1232                    // through a Solidity intermediary to reach an inner
1233                    // contract that clears storage loses the 4800-gas clearing
1234                    // refund (block 55,755,413 tx 2).
1235                    interpreter.gas.record_refund(sub_result.refund);
1236                }
1237            }
1238            InterpreterAction::NewFrame(FrameInput::Create(mut sub_create)) => {
1239                revm::Inspector::create(&mut multi_gas_inspector, context, &mut sub_create);
1240                // Dispatch create through our trampoline
1241                let salt = match sub_create.scheme() {
1242                    revm::interpreter::CreateScheme::Create2 { salt } => {
1243                        Some(B256::from(salt.to_be_bytes()))
1244                    }
1245                    _ => None,
1246                };
1247
1248                let parent_pages = read_tx_pages(pre_ctx);
1249                let sub_result = stylus_create_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
1250                    context as *mut _ as *mut (),
1251                    pre_ctx as *const _ as *const (),
1252                    sub_create.caller(),
1253                    sub_create.init_code(),
1254                    sub_create.gas_limit(),
1255                    sub_create.value(),
1256                    salt,
1257                    parent_pages,
1258                );
1259                write_tx_pages(pre_ctx, sub_result.pages);
1260
1261                let gas_remaining = sub_create.gas_limit().saturating_sub(sub_result.gas_cost);
1262                let created_addr = sub_result.address;
1263
1264                let ins_result = if created_addr.is_some() {
1265                    InstructionResult::Return
1266                } else if !sub_result.output.is_empty() {
1267                    InstructionResult::Revert
1268                } else {
1269                    InstructionResult::CreateInitCodeStartingEF00
1270                };
1271
1272                let output: Bytes = sub_result.output.into();
1273                interpreter.return_data.set_buffer(output);
1274
1275                // Push created address or zero
1276                let item = match created_addr {
1277                    Some(addr) => addr.into_word().into(),
1278                    None => U256::ZERO,
1279                };
1280                let _ = interpreter.stack.push(item);
1281
1282                if ins_result.is_ok_or_revert() {
1283                    interpreter.gas.erase_cost(gas_remaining);
1284                }
1285            }
1286            InterpreterAction::NewFrame(FrameInput::Empty) => {
1287                pre_ctx.add_stylus_multi_gas(multi_gas_inspector.take_multi_gas());
1288                return InterpreterResult::new(
1289                    InstructionResult::Revert,
1290                    Bytes::new(),
1291                    EvmGas::new(0),
1292                );
1293            }
1294        }
1295    }
1296}
1297
1298// ── Stylus WASM dispatch ────────────────────────────────────────────
1299
1300/// Exits the Stylus frame on drop.
1301struct StylusFrameGuard<'a> {
1302    ctx: &'a std::sync::Arc<arb_context::ArbPrecompileCtx>,
1303}
1304
1305impl Drop for StylusFrameGuard<'_> {
1306    fn drop(&mut self) {
1307        self.ctx.exit_stylus_frame();
1308    }
1309}
1310
1311/// Execute a Stylus WASM program by creating a NativeInstance and running it.
1312///
1313/// Validates the program, computes upfront gas costs (memory pages + init/cached
1314/// gas), deducts them, then runs the WASM. Page counters flow through
1315/// `TxCtx::stylus_pages_*` between dispatch boundaries.
1316fn execute_stylus_program<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
1317    context: &mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1318    inputs: &CallInputs,
1319    bytecode: &[u8],
1320    ctx: &std::sync::Arc<arb_context::ArbPrecompileCtx>,
1321) -> InterpreterResult
1322where
1323    BlockEnv: revm::context::Block,
1324    TxEnv: revm::context::Transaction,
1325    CfgEnv: revm::context::Cfg,
1326    DB: Database,
1327{
1328    use arbos::programs::types::UserOutcome;
1329
1330    let stylus_frame_depth = ctx.enter_stylus_frame();
1331    let _stylus_frame_guard = StylusFrameGuard { ctx };
1332
1333    let zero_gas = || EvmGas::new(0);
1334    let write_pages = |open: u16, ever: u16| {
1335        let mut tx = ctx.tx.lock();
1336        tx.stylus_pages_open = open;
1337        tx.stylus_pages_ever = ever;
1338    };
1339    let (parent_open, parent_ever) = {
1340        let tx = ctx.tx.lock();
1341        (tx.stylus_pages_open, tx.stylus_pages_ever)
1342    };
1343
1344    let code_hash = alloy_primitives::keccak256(bytecode);
1345    let arbos_version = ctx.block.arbos_version;
1346    let block_timestamp = ctx.block.block_timestamp;
1347
1348    let params_sto = programs_params_storage();
1349    let params = {
1350        let mut backend = JournalBackend::new(&mut context.journaled_state);
1351        match StylusParams::load(arbos_version, &params_sto, &mut backend) {
1352            Ok(p) => p,
1353            Err(e) => {
1354                tracing::warn!(target: "stylus", err = %e, "failed to load StylusParams from storage");
1355                return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1356            }
1357        }
1358    };
1359
1360    let program_word = match read_program_word(&mut context.journaled_state, code_hash) {
1361        Some(w) => w,
1362        None => {
1363            tracing::warn!(target: "stylus", codehash = %code_hash, "failed to read program data");
1364            return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1365        }
1366    };
1367    let program = Program::from_storage(program_word, block_timestamp);
1368
1369    if program.version == 0 || program.version != params.version {
1370        tracing::warn!(target: "stylus", codehash = %code_hash, program_ver = program.version, params_ver = params.version, "program version mismatch");
1371        return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1372    }
1373    let expiry_seconds = (params.expiry_days as u64) * 24 * 3600;
1374    if program.age_seconds > expiry_seconds {
1375        tracing::warn!(target: "stylus", codehash = %code_hash, "program expired");
1376        return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1377    }
1378
1379    let recent_wasms_hit = if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_60 {
1380        ctx.block.insert_recent_wasm(code_hash)
1381    } else {
1382        false
1383    };
1384    let effective_cached = program.cached || recent_wasms_hit;
1385    let effective_program = if effective_cached != program.cached {
1386        let mut p = program;
1387        p.cached = effective_cached;
1388        p
1389    } else {
1390        program
1391    };
1392    let upfront_cost = stylus_call_gas_cost(
1393        &params,
1394        &effective_program,
1395        parent_open,
1396        parent_ever,
1397        arbos_version,
1398    );
1399    let total_gas = inputs.gas_limit;
1400
1401    if total_gas < upfront_cost {
1402        // Only the outermost Stylus frame leaves its abort gas undimensioned; a
1403        // Stylus ancestor folds a nested abort into its own computation.
1404        if stylus_frame_depth == 1 {
1405            ctx.add_stylus_upfront_oog_gas(total_gas);
1406        }
1407        return InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), zero_gas());
1408    }
1409    let gas_for_wasm = total_gas - upfront_cost;
1410
1411    let stylus_config = StylusConfig::new(params.version, params.max_stack_depth, params.ink_price);
1412
1413    let target_addr = inputs.target_address;
1414    let reentrant = ctx.stylus_program_count(target_addr) > 1;
1415
1416    let module_hash =
1417        read_module_hash(&mut context.journaled_state, code_hash).unwrap_or(code_hash);
1418
1419    let mut evm_data = build_evm_data(context, inputs, ctx);
1420    evm_data.reentrant = reentrant as u32;
1421    evm_data.cached = effective_program.cached;
1422    evm_data.module_hash = module_hash;
1423
1424    let start_open = parent_open.saturating_add(program.footprint);
1425    let start_ever = parent_ever.max(start_open);
1426
1427    let journal_ptr = &mut context.journaled_state as *mut revm::Journal<DB>;
1428    let is_static = inputs.is_static || matches!(inputs.scheme, CallScheme::StaticCall);
1429    let ctx_ptr = context as *mut _ as *mut ();
1430    let precompile_ctx_ptr = std::sync::Arc::as_ptr(ctx) as *const ();
1431    let caller = inputs.caller;
1432    let call_value = inputs.value.get();
1433    // SAFETY: `journal_ptr`, `ctx_ptr`, and `precompile_ctx_ptr` are all
1434    // borrowed from values that live for the entirety of this function —
1435    // `context` is borrowed `&mut` through the call, the `Arc` holds `ctx`
1436    // pinned, and the journal is part of `context`. The `StylusEvmApi` is
1437    // consumed by `instance.run_main` below before this function returns,
1438    // so the pointers never outlive the borrows that produced them.
1439    let evm_api = unsafe {
1440        StylusEvmApi::new(
1441            journal_ptr,
1442            target_addr,
1443            caller,
1444            call_value,
1445            is_static,
1446            arbos_version,
1447            ctx_ptr,
1448            precompile_ctx_ptr,
1449            Some(stylus_call_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>),
1450            Some(stylus_create_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>),
1451        )
1452    };
1453
1454    // Reuse the program's compiled module from the process-wide cache, compiling
1455    // and inserting it on a miss. Keyed by module hash, independent of block/tx state.
1456    let long_term_tag = if program.cached { 1u32 } else { 0u32 };
1457    let (module, store) = match arb_stylus::cache::InitCache::get(
1458        module_hash,
1459        params.version,
1460        long_term_tag,
1461        false,
1462    ) {
1463        Some(loaded) => loaded,
1464        None => {
1465            // A root program's WASM is reconstructed from its fragments; execution
1466            // does not charge for the reads (the activation path does) and reads
1467            // fragment code straight from the database so it is not warmed.
1468            let decompressed_result = if arb_stylus::is_stylus_root(bytecode) {
1469                arb_stylus::get_wasm_from_root(
1470                    bytecode,
1471                    params.max_wasm_size,
1472                    params.max_fragment_count,
1473                    false,
1474                    |addr| {
1475                        let db = &mut context.journaled_state.database;
1476                        let info = db
1477                            .basic(addr)
1478                            .map_err(|e| arb_stylus::StylusError::Backend(format!("{e:?}")))?
1479                            .unwrap_or_default();
1480                        let code = match info.code {
1481                            Some(c) => c,
1482                            None => db
1483                                .code_by_hash(info.code_hash)
1484                                .map_err(|e| arb_stylus::StylusError::Backend(format!("{e:?}")))?,
1485                        };
1486                        Ok(code.original_bytes().to_vec())
1487                    },
1488                )
1489            } else {
1490                arb_stylus::decompress_wasm(bytecode)
1491            };
1492            let decompressed = match decompressed_result {
1493                Ok(w) => w,
1494                // A backing-store failure is an infrastructure error: abort rather
1495                // than revert, so a transient read error can't pass as a bad program.
1496                Err(arb_stylus::StylusError::Backend(e)) => {
1497                    tracing::error!(target: "stylus", codehash = %code_hash, err = %e, "fragment read failed");
1498                    write_pages(parent_open, start_ever);
1499                    return InterpreterResult::new(
1500                        InstructionResult::FatalExternalError,
1501                        Bytes::new(),
1502                        zero_gas(),
1503                    );
1504                }
1505                Err(e) => {
1506                    tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "WASM decompression failed");
1507                    write_pages(parent_open, start_ever);
1508                    return InterpreterResult::new(
1509                        InstructionResult::Revert,
1510                        Bytes::new(),
1511                        zero_gas(),
1512                    );
1513                }
1514            };
1515            let serialized = match arb_stylus::compile_module(&decompressed, params.version, false)
1516            {
1517                Ok(s) => s,
1518                Err(e) => {
1519                    tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "failed to compile WASM");
1520                    write_pages(parent_open, start_ever);
1521                    return InterpreterResult::new(
1522                        InstructionResult::Revert,
1523                        Bytes::new(),
1524                        zero_gas(),
1525                    );
1526                }
1527            };
1528            match arb_stylus::cache::InitCache::insert(
1529                module_hash,
1530                &serialized,
1531                params.version,
1532                long_term_tag,
1533                false,
1534            ) {
1535                Ok(loaded) => loaded,
1536                Err(e) => {
1537                    tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "failed to load compiled module");
1538                    write_pages(parent_open, start_ever);
1539                    return InterpreterResult::new(
1540                        InstructionResult::Revert,
1541                        Bytes::new(),
1542                        zero_gas(),
1543                    );
1544                }
1545            }
1546        }
1547    };
1548
1549    let compile = match arb_stylus::CompileConfig::version(params.version, false) {
1550        Ok(c) => c,
1551        Err(e) => {
1552            tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "unsupported Stylus version");
1553            write_pages(parent_open, start_ever);
1554            return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1555        }
1556    };
1557    let mut env = arb_stylus::env::WasmEnv::new(compile, Some(stylus_config), evm_api, evm_data);
1558    env.set_pages(
1559        start_open,
1560        start_ever,
1561        params.free_pages,
1562        params.page_gas,
1563        params.page_limit,
1564        arbos_version,
1565    );
1566    let mut instance = match arb_stylus::NativeInstance::from_module(module, store, env) {
1567        Ok(inst) => inst,
1568        Err(e) => {
1569            tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "failed to build instance");
1570            write_pages(parent_open, start_ever);
1571            return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1572        }
1573    };
1574
1575    write_pages(start_open, start_ever);
1576
1577    let ink = stylus_config.pricing.gas_to_ink(StylusGas(gas_for_wasm));
1578
1579    let calldata_owned: Bytes = inputs.input.bytes(context);
1580    let calldata: &[u8] = &calldata_owned;
1581    let outcome = match instance.run_main(calldata, stylus_config, ink) {
1582        Ok(outcome) => outcome,
1583        Err(e) => {
1584            tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "WASM execution failed");
1585            let final_ever = instance.env().pages_ever.max(start_ever);
1586            write_pages(parent_open, final_ever);
1587            return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1588        }
1589    };
1590
1591    let final_ever = instance.env().pages_ever.max(start_ever);
1592    write_pages(parent_open, final_ever);
1593
1594    let ink_left = match instance.ink_left() {
1595        arb_stylus::MachineMeter::Ready(ink_val) => ink_val,
1596        arb_stylus::MachineMeter::Exhausted => arb_stylus::Ink(0),
1597    };
1598    let gas_left = stylus_config.pricing.ink_to_gas(ink_left).0;
1599
1600    let output: Bytes = instance.env().outs.clone().into();
1601    let gas_left = if !output.is_empty()
1602        && arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_FIXES
1603    {
1604        let evm_cost = arbos::programs::types::evm_memory_cost(output.len() as u64);
1605        if total_gas < evm_cost {
1606            0
1607        } else {
1608            gas_left.min(total_gas - evm_cost)
1609        }
1610    } else {
1611        gas_left
1612    };
1613
1614    let mut gas_result = EvmGas::new(gas_left);
1615    let sstore_refund = instance.env().evm_api.sstore_refund();
1616    if sstore_refund != 0 {
1617        gas_result.record_refund(sstore_refund);
1618    }
1619
1620    let consumed_gas_left = match outcome {
1621        UserOutcome::OutOfInk | UserOutcome::OutOfStack => 0,
1622        _ => gas_left,
1623    };
1624    // Gas forwarded to sub-calls is attributed by the callee frame, so exclude
1625    // it from this frame's WasmComputation residual.
1626    let sub_call_gas = instance.env().evm_api.sub_call_gas();
1627    let mut program_multi_gas = instance.env().evm_api.multi_gas();
1628    arbos::programs::attribute_wasm_computation(
1629        &mut program_multi_gas,
1630        total_gas.saturating_sub(sub_call_gas),
1631        consumed_gas_left,
1632    );
1633    ctx.add_stylus_multi_gas(program_multi_gas);
1634
1635    match outcome {
1636        UserOutcome::Success => {
1637            InterpreterResult::new(InstructionResult::Return, output, gas_result)
1638        }
1639        UserOutcome::Revert => {
1640            InterpreterResult::new(InstructionResult::Revert, output, gas_result)
1641        }
1642        UserOutcome::OutOfInk => {
1643            InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), zero_gas())
1644        }
1645        UserOutcome::OutOfStack => {
1646            InterpreterResult::new(InstructionResult::CallTooDeep, Bytes::new(), zero_gas())
1647        }
1648        UserOutcome::Failure => {
1649            InterpreterResult::new(InstructionResult::Revert, Bytes::new(), gas_result)
1650        }
1651    }
1652}
1653
1654/// Build [`EvmData`] from the current execution context.
1655fn build_evm_data<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
1656    context: &revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1657    inputs: &CallInputs,
1658    ctx: &arb_context::ArbPrecompileCtx,
1659) -> EvmData
1660where
1661    BlockEnv: revm::context::Block,
1662    TxEnv: revm::context::Transaction,
1663    CfgEnv: revm::context::Cfg,
1664    DB: Database,
1665{
1666    let basefee = U256::from(context.block.basefee());
1667    // Pre-cap effective price stashed by ArbBlockExecutor; fall back to the
1668    // capped tx_env when unset (e.g. direct dispatch in unit tests).
1669    let stashed = ctx.tx_snapshot().effective_gas_price;
1670    let gas_price = if stashed != 0 {
1671        U256::from(stashed)
1672    } else {
1673        U256::from(context.tx.gas_price())
1674    };
1675    let value = inputs.value.get();
1676
1677    EvmData {
1678        arbos_version: ctx.block.arbos_version,
1679        block_basefee: B256::from(basefee.to_be_bytes()),
1680        chain_id: context.cfg.chain_id(),
1681        block_coinbase: context.block.beneficiary(),
1682        block_gas_limit: context.block.gas_limit(),
1683        block_number: ctx.block.l1_block_number_for_evm,
1684        block_timestamp: context.block.timestamp().saturating_to(),
1685        contract_address: inputs.target_address,
1686        module_hash: alloy_primitives::keccak256(b""),
1687        msg_sender: inputs.caller,
1688        msg_value: B256::from(value.to_be_bytes()),
1689        tx_gas_price: B256::from(gas_price.to_be_bytes()),
1690        tx_origin: context.tx.caller(),
1691        reentrant: 0,
1692        cached: false,
1693        tracing: false,
1694    }
1695}
1696
1697// ── Stylus frame-level intercept ──────────────────────────────────
1698
1699/// Check if a `FrameInit` targets a Stylus WASM program via `known_bytecode`.
1700fn is_stylus_call(frame_init: &FrameInit, arbos_version: u64) -> Option<Bytes> {
1701    if arbos_version < arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS {
1702        return None;
1703    }
1704    if let FrameInput::Call(ref inputs) = frame_init.frame_input
1705        && let Some((_, ref code)) = inputs.known_bytecode
1706    {
1707        let raw = code.original_bytes();
1708        if arb_stylus::is_stylus_runnable(&raw) {
1709            return Some(raw);
1710        }
1711    }
1712    None
1713}
1714
1715/// Execute a Stylus call and return the appropriate `FrameResult`, handling
1716/// journal checkpoint commit/revert.
1717fn execute_stylus_call_concrete<DB: Database>(
1718    ctx: &mut EthEvmContext<DB>,
1719    inputs: &CallInputs,
1720    bytecode: &[u8],
1721    checkpoint: revm::context_interface::journaled_state::JournalCheckpoint,
1722    pre_ctx: &std::sync::Arc<arb_context::ArbPrecompileCtx>,
1723) -> FrameResult {
1724    // Handle value transfer for non-delegate calls (matches EthFrame::make_call_frame).
1725    if let revm::interpreter::CallValue::Transfer(value) = inputs.value
1726        && let Some(i) =
1727            ctx.journal_mut()
1728                .transfer_loaded(inputs.caller, inputs.target_address, value)
1729    {
1730        let gas = EvmGas::new(inputs.gas_limit);
1731        ctx.journaled_state.inner.checkpoint_revert(checkpoint);
1732        return FrameResult::Call(CallOutcome {
1733            result: InterpreterResult::new(i.into(), Bytes::new(), gas),
1734            memory_offset: inputs.return_memory_offset.clone(),
1735            was_precompile_called: false,
1736            precompile_call_logs: Vec::new(),
1737        });
1738    }
1739
1740    let result = execute_stylus_program(ctx, inputs, bytecode, pre_ctx);
1741
1742    if result.result.is_ok() {
1743        ctx.journaled_state.inner.checkpoint_commit();
1744    } else {
1745        ctx.journaled_state.inner.checkpoint_revert(checkpoint);
1746    }
1747    FrameResult::Call(CallOutcome {
1748        result,
1749        memory_offset: inputs.return_memory_offset.clone(),
1750        was_precompile_called: false,
1751        precompile_call_logs: Vec::new(),
1752    })
1753}
1754
1755// ── Precompile provider ────────────────────────────────────────────
1756
1757#[derive(Clone, Debug)]
1758pub struct ArbPrecompilesMap {
1759    pub inner: PrecompilesMap,
1760    /// Per-block context shared with the registered precompile closures and
1761    /// the Stylus dispatch path. Cheap to clone (`Arc`).
1762    pub ctx: std::sync::Arc<arb_context::ArbPrecompileCtx>,
1763}
1764
1765impl ArbPrecompilesMap {
1766    pub fn new(inner: PrecompilesMap, ctx: std::sync::Arc<arb_context::ArbPrecompileCtx>) -> Self {
1767        Self { inner, ctx }
1768    }
1769}
1770
1771impl<BlockEnv, TxEnv, CfgEnv, DB, Chain>
1772    PrecompileProvider<revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>>
1773    for ArbPrecompilesMap
1774where
1775    BlockEnv: revm::context::Block,
1776    TxEnv: revm::context::Transaction,
1777    CfgEnv: revm::context::Cfg,
1778    DB: Database,
1779{
1780    type Output = InterpreterResult;
1781
1782    fn set_spec(&mut self, spec: CfgEnv::Spec) -> bool {
1783        <PrecompilesMap as PrecompileProvider<
1784            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1785        >>::set_spec(&mut self.inner, spec)
1786    }
1787
1788    // The `String` error type comes from upstream `PrecompileProvider::run`;
1789    // we cannot tighten it without forking revm.
1790    fn run(
1791        &mut self,
1792        context: &mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1793        inputs: &CallInputs,
1794    ) -> Result<Option<Self::Output>, String> {
1795        self.ctx.set_evm_depth(context.journaled_state.inner.depth);
1796
1797        // Check precompiles first.
1798        if let result @ Some(_) = <PrecompilesMap as PrecompileProvider<
1799            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1800        >>::run(&mut self.inner, context, inputs)?
1801        {
1802            return Ok(result);
1803        }
1804
1805        // Check for Stylus WASM programs (active at ArbOS v31+).
1806        let arbos_version = self.ctx.block.arbos_version;
1807        if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS {
1808            // Use known_bytecode from CallInputs if available (already loaded by
1809            // revm's CALL handler), otherwise load from journal.
1810            let bytecode = inputs
1811                .known_bytecode
1812                .as_ref()
1813                .map(|(_, code)| code.original_bytes())
1814                .or_else(|| {
1815                    context
1816                        .journaled_state
1817                        .inner
1818                        .load_code(
1819                            &mut context.journaled_state.database,
1820                            inputs.bytecode_address,
1821                        )
1822                        .ok()
1823                        .and_then(|acc| acc.data.info.code.as_ref().map(|c| c.original_bytes()))
1824                });
1825
1826            if let Some(bytecode) = bytecode
1827                && arb_stylus::is_stylus_runnable(&bytecode)
1828            {
1829                return Ok(Some(execute_stylus_program(
1830                    context, inputs, &bytecode, &self.ctx,
1831                )));
1832            }
1833        }
1834
1835        Ok(None)
1836    }
1837
1838    fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
1839        <PrecompilesMap as PrecompileProvider<
1840            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1841        >>::warm_addresses(&self.inner)
1842    }
1843
1844    fn contains(&self, address: &Address) -> bool {
1845        <PrecompilesMap as PrecompileProvider<
1846            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1847        >>::contains(&self.inner, address)
1848    }
1849}
1850
1851// ── ArbEvm ─────────────────────────────────────────────────────────
1852
1853type InnerRevmEvm<DB, I> = RevmEvm<
1854    EthEvmContext<DB>,
1855    I,
1856    EthInstructions<EthInterpreter, EthEvmContext<DB>>,
1857    ArbPrecompilesMap,
1858    EthFrame,
1859>;
1860
1861struct CreateFrameCtx {
1862    caller: Address,
1863    checkpoint: revm::context_interface::journaled_state::JournalCheckpoint,
1864}
1865
1866/// Arbitrum EVM with frame-level Stylus dispatch and custom opcodes.
1867///
1868/// Implements [`EvmTr`] directly to intercept Stylus WASM calls in
1869/// `frame_init` before they reach the precompile provider.
1870pub struct ArbEvm<DB: Database, I> {
1871    inner: InnerRevmEvm<DB, I>,
1872    inspect: bool,
1873    create_ctx_stack: Vec<CreateFrameCtx>,
1874    actor_stack: Vec<Option<Address>>,
1875}
1876
1877impl<DB, I> ArbEvm<DB, I>
1878where
1879    DB: Database,
1880{
1881    pub fn new(inner: InnerRevmEvm<DB, I>, inspect: bool) -> Self {
1882        Self {
1883            inner,
1884            inspect,
1885            create_ctx_stack: Vec::new(),
1886            actor_stack: Vec::new(),
1887        }
1888    }
1889
1890    pub fn into_inner(self) -> InnerRevmEvm<DB, I> {
1891        self.inner
1892    }
1893
1894    pub fn ctx(&self) -> &EthEvmContext<DB> {
1895        &self.inner.ctx
1896    }
1897
1898    pub fn ctx_mut(&mut self) -> &mut EthEvmContext<DB> {
1899        &mut self.inner.ctx
1900    }
1901
1902    pub fn precompiles_mut(&mut self) -> &mut ArbPrecompilesMap {
1903        &mut self.inner.precompiles
1904    }
1905}
1906
1907/// Accessor for the `Arc<ArbPrecompileCtx>` that an EVM factory most recently
1908/// staged for the next [`EvmFactory::create_evm`] call. The block executor
1909/// reuses this `Arc` so its per-tx writes share an allocation with the EVM
1910/// precompile handler closures (which capture the `Arc` at registration time).
1911pub trait ArbEvmFactoryStaged {
1912    fn staged_precompile_ctx(&self) -> Option<std::sync::Arc<arb_context::ArbPrecompileCtx>>;
1913}
1914
1915impl ArbEvmFactoryStaged for ArbEvmFactory {
1916    fn staged_precompile_ctx(&self) -> Option<std::sync::Arc<arb_context::ArbPrecompileCtx>> {
1917        self.staged()
1918    }
1919}
1920
1921impl<DB: Database, I> core::ops::Deref for ArbEvm<DB, I> {
1922    type Target = EthEvmContext<DB>;
1923    fn deref(&self) -> &Self::Target {
1924        &self.inner.ctx
1925    }
1926}
1927
1928impl<DB: Database, I> core::ops::DerefMut for ArbEvm<DB, I> {
1929    fn deref_mut(&mut self) -> &mut Self::Target {
1930        &mut self.inner.ctx
1931    }
1932}
1933
1934// ── EvmTr for ArbEvm ─────────────────────────────────────────────
1935
1936impl<DB, I> EvmTr for ArbEvm<DB, I>
1937where
1938    DB: Database,
1939    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
1940{
1941    type Context = EthEvmContext<DB>;
1942    type Instructions = EthInstructions<EthInterpreter, EthEvmContext<DB>>;
1943    type Precompiles = ArbPrecompilesMap;
1944    type Frame = EthFrame<EthInterpreter>;
1945
1946    #[inline]
1947    fn all(
1948        &self,
1949    ) -> (
1950        &Self::Context,
1951        &Self::Instructions,
1952        &Self::Precompiles,
1953        &FrameStack<Self::Frame>,
1954    ) {
1955        self.inner.all()
1956    }
1957
1958    #[inline]
1959    fn all_mut(
1960        &mut self,
1961    ) -> (
1962        &mut Self::Context,
1963        &mut Self::Instructions,
1964        &mut Self::Precompiles,
1965        &mut FrameStack<Self::Frame>,
1966    ) {
1967        self.inner.all_mut()
1968    }
1969
1970    #[inline]
1971    fn frame_init(
1972        &mut self,
1973        frame_input: FrameInit,
1974    ) -> Result<
1975        ItemOrResult<&mut Self::Frame, FrameResult>,
1976        revm::handler::evm::ContextDbError<Self::Context>,
1977    > {
1978        let pre_ctx = self.inner.precompiles.ctx.clone();
1979        let pushed_caller = match &frame_input.frame_input {
1980            FrameInput::Call(inputs) => {
1981                pre_ctx.push_caller(inputs.caller);
1982                true
1983            }
1984            FrameInput::Create(inputs) => {
1985                pre_ctx.push_caller(inputs.caller());
1986                true
1987            }
1988            _ => false,
1989        };
1990
1991        match &frame_input.frame_input {
1992            FrameInput::Call(inputs)
1993                if !matches!(
1994                    inputs.scheme,
1995                    CallScheme::DelegateCall | CallScheme::CallCode
1996                ) =>
1997            {
1998                pre_ctx.push_stylus_program(inputs.target_address);
1999                self.actor_stack.push(Some(inputs.target_address));
2000            }
2001            FrameInput::Call(_) => {
2002                self.actor_stack.push(None);
2003            }
2004            FrameInput::Create(_) => {
2005                self.actor_stack.push(None);
2006            }
2007            _ => {}
2008        }
2009
2010        if let FrameInput::Create(inputs) = &frame_input.frame_input {
2011            let cp = self.inner.ctx.journal_mut().checkpoint();
2012            self.create_ctx_stack.push(CreateFrameCtx {
2013                caller: inputs.caller(),
2014                checkpoint: cp,
2015            });
2016        }
2017
2018        if let Some(bytecode) = is_stylus_call(&frame_input, pre_ctx.block.arbos_version)
2019            && let FrameInput::Call(ref inputs) = frame_input.frame_input
2020        {
2021            if frame_input.depth > revm::primitives::constants::CALL_STACK_LIMIT as usize {
2022                let gas = EvmGas::new(inputs.gas_limit);
2023                if pushed_caller {
2024                    pre_ctx.pop_caller();
2025                }
2026                return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
2027                    result: InterpreterResult::new(
2028                        InstructionResult::CallTooDeep,
2029                        Bytes::new(),
2030                        gas,
2031                    ),
2032                    memory_offset: inputs.return_memory_offset.clone(),
2033                    was_precompile_called: false,
2034                    precompile_call_logs: Vec::new(),
2035                })));
2036            }
2037            let checkpoint = self.inner.ctx.journal_mut().checkpoint();
2038            let result = execute_stylus_call_concrete(
2039                &mut self.inner.ctx,
2040                inputs,
2041                &bytecode,
2042                checkpoint,
2043                &pre_ctx,
2044            );
2045            if pushed_caller {
2046                pre_ctx.pop_caller();
2047            }
2048            return Ok(ItemOrResult::Result(result));
2049        }
2050
2051        self.inner.frame_init(frame_input)
2052    }
2053
2054    #[inline]
2055    fn frame_run(
2056        &mut self,
2057    ) -> Result<
2058        ItemOrResult<FrameInit, FrameResult>,
2059        revm::handler::evm::ContextDbError<Self::Context>,
2060    > {
2061        self.inner.frame_run()
2062    }
2063
2064    #[inline]
2065    fn frame_return_result(
2066        &mut self,
2067        mut result: FrameResult,
2068    ) -> Result<Option<FrameResult>, revm::handler::evm::ContextDbError<Self::Context>> {
2069        let pre_ctx = self.inner.precompiles.ctx.clone();
2070        pre_ctx.pop_caller();
2071
2072        if let Some(Some(addr)) = self.actor_stack.pop() {
2073            pre_ctx.pop_stylus_program(addr);
2074        }
2075
2076        // EIP-3541: reject runtime starting with 0xEF unless it's the Stylus
2077        // marker (0xEF 0xF0 0x00). revm's global check is disabled so Stylus
2078        // can deploy; we re-apply it here with the Stylus exception.
2079        if let FrameResult::Create(ref mut outcome) = result {
2080            let create_ctx = self.create_ctx_stack.pop();
2081            if outcome.instruction_result().is_ok()
2082                && let Some(addr) = outcome.address
2083            {
2084                let code_bytes: Vec<u8> = self
2085                    .inner
2086                    .ctx
2087                    .journal_mut()
2088                    .code(addr)
2089                    .map(|c| c.data.to_vec())
2090                    .unwrap_or_default();
2091                let starts_with_ef = code_bytes.first() == Some(&0xEF);
2092                let is_stylus =
2093                    arb_stylus::is_stylus_component(&code_bytes, pre_ctx.block.arbos_version);
2094                if starts_with_ef && !is_stylus {
2095                    if let Some(create_ctx) = create_ctx {
2096                        self.inner
2097                            .ctx
2098                            .journal_mut()
2099                            .checkpoint_revert(create_ctx.checkpoint);
2100                        use revm::context_interface::journaled_state::account::JournaledAccountTr;
2101                        if let Ok(mut caller_acc) = self
2102                            .inner
2103                            .ctx
2104                            .journal_mut()
2105                            .load_account_mut(create_ctx.caller)
2106                        {
2107                            let _ = caller_acc.data.bump_nonce();
2108                        }
2109                    }
2110                    outcome.address = None;
2111                    outcome.result.result = InstructionResult::CreateContractStartingWithEF;
2112                    outcome.result.output = Bytes::new();
2113                    outcome.result.gas.spend_all();
2114                }
2115            }
2116        }
2117        self.inner.frame_return_result(result)
2118    }
2119}
2120
2121// ── ExecuteEvm for ArbEvm ────────────────────────────────────────
2122
2123impl<DB, I> ExecuteEvm for ArbEvm<DB, I>
2124where
2125    DB: Database,
2126    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
2127{
2128    type ExecutionResult = ExecutionResult<HaltReason>;
2129    type State = revm::state::EvmState;
2130    type Error = EVMError<<DB as revm::Database>::Error, InvalidTransaction>;
2131    type Tx = revm::context::TxEnv;
2132    type Block = revm::context::BlockEnv;
2133
2134    #[inline]
2135    fn transact_one(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
2136        self.inner.ctx.set_tx(tx);
2137        MainnetHandler::default().run(self)
2138    }
2139
2140    #[inline]
2141    fn finalize(&mut self) -> Self::State {
2142        self.inner.ctx.journal_mut().finalize()
2143    }
2144
2145    #[inline]
2146    fn set_block(&mut self, block: Self::Block) {
2147        self.inner.ctx.set_block(block);
2148    }
2149
2150    #[inline]
2151    fn replay(&mut self) -> Result<ResultAndState<HaltReason>, Self::Error> {
2152        MainnetHandler::default().run(self).map(|result| {
2153            let state = self.finalize();
2154            ResultAndState::new(result, state)
2155        })
2156    }
2157}
2158
2159// ── SystemCallEvm for ArbEvm ─────────────────────────────────────
2160
2161impl<DB, I> SystemCallEvm for ArbEvm<DB, I>
2162where
2163    DB: Database,
2164    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
2165{
2166    fn system_call_one_with_caller(
2167        &mut self,
2168        caller: Address,
2169        system_contract_address: Address,
2170        data: Bytes,
2171    ) -> Result<Self::ExecutionResult, Self::Error> {
2172        use revm::handler::system_call::SystemCallTx;
2173        self.inner
2174            .ctx
2175            .set_tx(revm::context::TxEnv::new_system_tx_with_caller(
2176                caller,
2177                system_contract_address,
2178                data,
2179            ));
2180        MainnetHandler::default().run_system_call(self)
2181    }
2182}
2183
2184// ── InspectorEvmTr for ArbEvm ────────────────────────────────────
2185
2186impl<DB, I> revm::inspector::InspectorEvmTr for ArbEvm<DB, I>
2187where
2188    DB: Database,
2189    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
2190    revm::Journal<DB>: revm::inspector::JournalExt,
2191{
2192    type Inspector = I;
2193
2194    fn all_inspector(
2195        &self,
2196    ) -> (
2197        &Self::Context,
2198        &Self::Instructions,
2199        &Self::Precompiles,
2200        &FrameStack<Self::Frame>,
2201        &Self::Inspector,
2202    ) {
2203        let (ctx, inst, pre, fs) = self.inner.all();
2204        (ctx, inst, pre, fs, &self.inner.inspector)
2205    }
2206
2207    fn all_mut_inspector(
2208        &mut self,
2209    ) -> (
2210        &mut Self::Context,
2211        &mut Self::Instructions,
2212        &mut Self::Precompiles,
2213        &mut FrameStack<Self::Frame>,
2214        &mut Self::Inspector,
2215    ) {
2216        (
2217            &mut self.inner.ctx,
2218            &mut self.inner.instruction,
2219            &mut self.inner.precompiles,
2220            &mut self.inner.frame_stack,
2221            &mut self.inner.inspector,
2222        )
2223    }
2224}
2225
2226// ── InspectEvm for ArbEvm ────────────────────────────────────────
2227
2228impl<DB, I> InspectEvm for ArbEvm<DB, I>
2229where
2230    DB: Database,
2231    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
2232    revm::Journal<DB>: revm::inspector::JournalExt,
2233{
2234    type Inspector = I;
2235
2236    fn set_inspector(&mut self, inspector: Self::Inspector) {
2237        self.inner.inspector = inspector;
2238    }
2239
2240    fn inspect_one_tx(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
2241        self.inner.ctx.set_tx(tx);
2242        MainnetHandler::default().inspect_run(self)
2243    }
2244}
2245
2246// ── alloy_evm::Evm for ArbEvm ───────────────────────────────────
2247
2248impl<DB, I> Evm for ArbEvm<DB, I>
2249where
2250    DB: Database,
2251    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
2252    revm::Journal<DB>: revm::inspector::JournalExt,
2253{
2254    type DB = DB;
2255    type Tx = ArbTransaction;
2256    type Error = EVMError<<DB as revm::Database>::Error>;
2257    type HaltReason = HaltReason;
2258    type Spec = SpecId;
2259    type Precompiles = PrecompilesMap;
2260    type Inspector = I;
2261    type BlockEnv = revm::context::BlockEnv;
2262
2263    fn block(&self) -> &revm::context::BlockEnv {
2264        &self.inner.ctx.block
2265    }
2266
2267    fn chain_id(&self) -> u64 {
2268        self.inner.ctx.cfg.chain_id
2269    }
2270
2271    fn transact_raw(
2272        &mut self,
2273        tx: Self::Tx,
2274    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
2275        if self.inspect {
2276            InspectEvm::inspect_tx(self, tx.into_inner())
2277        } else {
2278            ExecuteEvm::transact(self, tx.into_inner())
2279        }
2280    }
2281
2282    fn transact_system_call(
2283        &mut self,
2284        caller: Address,
2285        contract: Address,
2286        data: Bytes,
2287    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
2288        SystemCallEvm::system_call_with_caller(self, caller, contract, data)
2289    }
2290
2291    fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) {
2292        let revm::Context {
2293            block: block_env,
2294            cfg: cfg_env,
2295            journaled_state,
2296            ..
2297        } = self.inner.ctx;
2298        (journaled_state.database, EvmEnv { block_env, cfg_env })
2299    }
2300
2301    fn set_inspector_enabled(&mut self, enabled: bool) {
2302        self.inspect = enabled;
2303    }
2304
2305    fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
2306        (
2307            &self.inner.ctx.journaled_state.database,
2308            &self.inner.inspector,
2309            &self.inner.precompiles.inner,
2310        )
2311    }
2312
2313    fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
2314        (
2315            &mut self.inner.ctx.journaled_state.database,
2316            &mut self.inner.inspector,
2317            &mut self.inner.precompiles.inner,
2318        )
2319    }
2320}
2321
2322// ── ArbEvmFactory ──────────────────────────────────────────────────
2323
2324/// Factory for creating Arbitrum EVM instances with custom precompiles.
2325///
2326/// `staged_ctx` is the bridge that lets [`evm_env`](crate::config) on one
2327/// thread hand a freshly-built [`ArbPrecompileCtx`] to [`create_evm`] on
2328/// another (reth's RPC dispatcher uses `spawn_blocking`). The executor
2329/// path writes through the same slot before each block.
2330#[derive(Default, Debug)]
2331pub struct ArbEvmFactory {
2332    pub inner: alloy_evm::EthEvmFactory,
2333    staged_ctx:
2334        std::sync::Arc<parking_lot::RwLock<Option<std::sync::Arc<arb_context::ArbPrecompileCtx>>>>,
2335    chain_caches: std::sync::Arc<arb_context::ChainCaches>,
2336    /// When set, each clone gets its own staging slot and chain caches.
2337    /// Parallel offline executors need that isolation (the caches window-evict,
2338    /// so sharing them across workers clobbers each other); the live path
2339    /// shares both across clones for the cross-thread RPC handoff.
2340    isolate_staging_on_clone: bool,
2341}
2342
2343impl Clone for ArbEvmFactory {
2344    fn clone(&self) -> Self {
2345        let isolate = self.isolate_staging_on_clone;
2346        Self {
2347            inner: self.inner,
2348            staged_ctx: if isolate {
2349                std::sync::Arc::default()
2350            } else {
2351                self.staged_ctx.clone()
2352            },
2353            chain_caches: if isolate {
2354                std::sync::Arc::default()
2355            } else {
2356                self.chain_caches.clone()
2357            },
2358            isolate_staging_on_clone: isolate,
2359        }
2360    }
2361}
2362
2363impl ArbEvmFactory {
2364    pub fn new() -> Self {
2365        Self::default()
2366    }
2367
2368    /// Factory whose clones each receive an independent staging slot, for
2369    /// parallel offline block execution (`re-execute`/`import`/`stage`).
2370    pub fn isolated() -> Self {
2371        Self {
2372            isolate_staging_on_clone: true,
2373            ..Self::default()
2374        }
2375    }
2376
2377    /// Stage the per-block context that `create_evm` will install on the
2378    /// EVM-execution thread.
2379    pub fn stage_ctx(&self, ctx: std::sync::Arc<arb_context::ArbPrecompileCtx>) {
2380        *self.staged_ctx.write() = Some(ctx);
2381    }
2382
2383    pub fn chain_caches(&self) -> &std::sync::Arc<arb_context::ChainCaches> {
2384        &self.chain_caches
2385    }
2386
2387    fn staged(&self) -> Option<std::sync::Arc<arb_context::ArbPrecompileCtx>> {
2388        self.staged_ctx.read().clone()
2389    }
2390}
2391
2392fn build_arb_evm<DB: Database, I>(
2393    inner: RevmEvm<
2394        EthEvmContext<DB>,
2395        I,
2396        EthInstructions<EthInterpreter, EthEvmContext<DB>>,
2397        PrecompilesMap,
2398        EthFrame,
2399    >,
2400    staged: Option<std::sync::Arc<arb_context::ArbPrecompileCtx>>,
2401    inspect: bool,
2402) -> ArbEvm<DB, I> {
2403    let pre_ctx =
2404        staged.unwrap_or_else(|| std::sync::Arc::new(arb_context::ArbPrecompileCtx::default()));
2405    let RevmEvm {
2406        ctx: evm_ctx,
2407        inspector,
2408        mut instruction,
2409        mut precompiles,
2410        frame_stack: _,
2411    } = inner;
2412
2413    instruction.insert_instruction(
2414        BLOBBASEFEE_OPCODE,
2415        revm::interpreter::Instruction::new(arb_blob_basefee, 2),
2416    );
2417    instruction.insert_instruction(
2418        SELFDESTRUCT_OPCODE,
2419        revm::interpreter::Instruction::new(arb_selfdestruct, 5000),
2420    );
2421    instruction.insert_instruction(
2422        NUMBER_OPCODE,
2423        revm::interpreter::Instruction::new(arb_number, 2),
2424    );
2425    instruction.insert_instruction(
2426        BLOCKHASH_OPCODE,
2427        revm::interpreter::Instruction::new(arb_blockhash, 20),
2428    );
2429    instruction.insert_instruction(
2430        BALANCE_OPCODE,
2431        revm::interpreter::Instruction::new(arb_balance, 0),
2432    );
2433    instruction.insert_instruction(
2434        SELFBALANCE_OPCODE,
2435        revm::interpreter::Instruction::new(arb_selfbalance, 5),
2436    );
2437    register_arb_precompiles(&mut precompiles, pre_ctx.clone());
2438    let arb_precompiles = ArbPrecompilesMap::new(precompiles, pre_ctx);
2439
2440    let revm_evm = RevmEvm::new_with_inspector(evm_ctx, inspector, instruction, arb_precompiles);
2441    ArbEvm::new(revm_evm, inspect)
2442}
2443
2444impl EvmFactory for ArbEvmFactory {
2445    type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> = ArbEvm<DB, I>;
2446    type Context<DB: Database> = EthEvmContext<DB>;
2447    type Tx = ArbTransaction;
2448    type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
2449    type HaltReason = HaltReason;
2450    type Spec = SpecId;
2451    type Precompiles = PrecompilesMap;
2452    type BlockEnv = revm::context::BlockEnv;
2453
2454    fn create_evm<DB: Database>(
2455        &self,
2456        db: DB,
2457        input: EvmEnv<Self::Spec>,
2458    ) -> Self::Evm<DB, NoOpInspector> {
2459        let eth_evm = self.inner.create_evm(db, input);
2460        build_arb_evm(eth_evm.into_inner(), self.staged(), false)
2461    }
2462
2463    fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>, EthInterpreter>>(
2464        &self,
2465        db: DB,
2466        input: EvmEnv<Self::Spec>,
2467        inspector: I,
2468    ) -> Self::Evm<DB, I> {
2469        let eth_evm = self.inner.create_evm_with_inspector(db, input, inspector);
2470        build_arb_evm(eth_evm.into_inner(), self.staged(), true)
2471    }
2472}
2473
2474#[cfg(test)]
2475mod tests {
2476    use std::sync::Arc;
2477
2478    use super::ArbEvmFactory;
2479
2480    #[test]
2481    fn isolated_clone_has_independent_staging_and_caches() {
2482        let factory = ArbEvmFactory::isolated();
2483        let clone = factory.clone();
2484        factory.stage_ctx(Arc::new(arb_context::ArbPrecompileCtx::default()));
2485        assert!(factory.staged().is_some());
2486        assert!(clone.staged().is_none());
2487        assert!(!Arc::ptr_eq(factory.chain_caches(), clone.chain_caches()));
2488    }
2489
2490    #[test]
2491    fn default_clone_shares_staging_and_caches() {
2492        let factory = ArbEvmFactory::new();
2493        let clone = factory.clone();
2494        factory.stage_ctx(Arc::new(arb_context::ArbPrecompileCtx::default()));
2495        assert!(clone.staged().is_some());
2496        assert!(Arc::ptr_eq(factory.chain_caches(), clone.chain_caches()));
2497    }
2498}