arb_evm/
evm.rs

1use alloy_evm::{
2    eth::EthEvmContext, precompiles::PrecompilesMap, Database, Evm, EvmEnv, EvmFactory,
3};
4use alloy_primitives::{Address, Bytes, B256, U256};
5use arb_precompiles::register_arb_precompiles;
6use arb_stylus::{
7    config::StylusConfig, ink::Gas as StylusGas, meter::MeteredMachine, run::RunProgram,
8    StylusEvmApi,
9};
10use arbos::programs::types::EvmData;
11use core::fmt::Debug;
12use revm::{
13    context::{
14        result::{EVMError, ExecutionResult, InvalidTransaction},
15        ContextSetters, Evm as RevmEvm, FrameStack,
16    },
17    context_interface::{
18        host::LoadError,
19        result::{HaltReason, ResultAndState},
20        ContextTr, JournalTr,
21    },
22    handler::{
23        instructions::EthInstructions, EthFrame, EvmTr, FrameResult, Handler, ItemOrResult,
24        MainnetHandler, PrecompileProvider,
25    },
26    inspector::{InspectorHandler, NoOpInspector},
27    interpreter::{
28        interpreter::EthInterpreter,
29        interpreter_action::FrameInit,
30        interpreter_types::{InputsTr, ReturnData, RuntimeFlag, StackTr},
31        CallInput, CallInputs, CallOutcome, CallScheme, FrameInput, Gas as EvmGas, Host,
32        InstructionContext, InstructionResult, InterpreterResult, InterpreterTypes,
33    },
34    primitives::hardfork::SpecId,
35    ExecuteEvm, InspectEvm, Inspector, SystemCallEvm,
36};
37
38use crate::transaction::ArbTransaction;
39
40/// BLOBBASEFEE opcode (0x4a).
41const BLOBBASEFEE_OPCODE: u8 = 0x4a;
42
43/// SELFDESTRUCT opcode (0xff).
44const SELFDESTRUCT_OPCODE: u8 = 0xff;
45
46/// NUMBER opcode (0x43).
47const NUMBER_OPCODE: u8 = 0x43;
48
49/// BLOCKHASH opcode (0x40).
50const BLOCKHASH_OPCODE: u8 = 0x40;
51
52/// BALANCE opcode (0x31).
53const BALANCE_OPCODE: u8 = 0x31;
54
55/// Arbitrum NUMBER: returns the L1 block number recorded during StartBlock.
56fn arb_number<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
57    let l1_block = arb_precompiles::get_l1_block_number_for_evm();
58    if !ctx.interpreter.stack.push(U256::from(l1_block)) {
59        ctx.interpreter.halt(InstructionResult::StackOverflow);
60    }
61}
62
63/// Arbitrum BLOCKHASH: uses L1 block number for range check.
64///
65/// Standard BLOCKHASH compares the requested block number against block_env.number,
66/// which is the L2 block number. Since Arbitrum's NUMBER opcode returns the L1
67/// block number, BLOCKHASH must also use L1 block numbers for the range check.
68/// Otherwise requests for L1 block hashes would always be out of range.
69fn arb_blockhash<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
70    use revm::interpreter::InstructionResult;
71
72    let requested = match ctx.interpreter.stack.pop() {
73        Some(v) => v,
74        None => {
75            ctx.interpreter.halt(InstructionResult::StackUnderflow);
76            return;
77        }
78    };
79
80    let l1_block_number = U256::from(arb_precompiles::get_l1_block_number_for_evm());
81
82    let Some(diff) = l1_block_number.checked_sub(requested) else {
83        if !ctx.interpreter.stack.push(U256::ZERO) {
84            ctx.interpreter.halt(InstructionResult::StackOverflow);
85        }
86        return;
87    };
88
89    let diff_u64: u64 = diff.try_into().unwrap_or(u64::MAX);
90    if diff_u64 == 0 || diff_u64 > 256 {
91        if !ctx.interpreter.stack.push(U256::ZERO) {
92            ctx.interpreter.halt(InstructionResult::StackOverflow);
93        }
94        return;
95    }
96
97    let requested_u64: u64 = requested.try_into().unwrap_or(u64::MAX);
98    match ctx.host.block_hash(requested_u64) {
99        Some(hash) => {
100            if !ctx.interpreter.stack.push(U256::from_be_bytes(hash.0)) {
101                ctx.interpreter.halt(InstructionResult::StackOverflow);
102            }
103        }
104        None => {
105            ctx.interpreter.halt_fatal();
106        }
107    }
108}
109
110// SHA3 tracer removed — can't easily wrap standard handler
111
112/// Arbitrum BALANCE: subtracts the poster-fee correction when a contract
113/// reads the transaction sender's balance, so that the observed value matches
114/// a full-`gas_limit * basefee` buy-gas charge.
115fn arb_balance<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
116    // Pop address from stack
117    let addr_u256 = match ctx.interpreter.stack.pop() {
118        Some(v) => v,
119        None => {
120            ctx.interpreter
121                .halt(revm::interpreter::InstructionResult::StackUnderflow);
122            return;
123        }
124    };
125
126    let addr = alloy_primitives::Address::from_word(alloy_primitives::B256::from(
127        addr_u256.to_be_bytes::<32>(),
128    ));
129
130    // Load account via host (handles cold/warm tracking)
131    let spec_id = ctx.interpreter.runtime_flag.spec_id();
132    if spec_id.is_enabled_in(revm::primitives::hardfork::SpecId::BERLIN) {
133        // Berlin+: use balance() which tracks cold/warm
134        let Some(state_load) = ctx.host.balance(addr) else {
135            ctx.interpreter.halt_fatal();
136            return;
137        };
138        // Charge gas: 2600 for cold, 100 for warm
139        let gas_cost = if state_load.is_cold { 2600u64 } else { 100u64 };
140        if !ctx.interpreter.gas.record_cost(gas_cost) {
141            ctx.interpreter
142                .halt(revm::interpreter::InstructionResult::OutOfGas);
143            return;
144        }
145
146        // Apply poster fee correction for sender
147        let balance = if addr == arb_precompiles::get_current_tx_sender() {
148            state_load
149                .data
150                .saturating_sub(arb_precompiles::get_poster_balance_correction())
151        } else {
152            state_load.data
153        };
154
155        if !ctx.interpreter.stack.push(balance) {
156            ctx.interpreter
157                .halt(revm::interpreter::InstructionResult::StackOverflow);
158        }
159    } else {
160        // Pre-Berlin: always 400 gas, load via basic path
161        let Some(state_load) = ctx.host.balance(addr) else {
162            ctx.interpreter.halt_fatal();
163            return;
164        };
165
166        let balance = if addr == arb_precompiles::get_current_tx_sender() {
167            state_load
168                .data
169                .saturating_sub(arb_precompiles::get_poster_balance_correction())
170        } else {
171            state_load.data
172        };
173
174        if !ctx.interpreter.stack.push(balance) {
175            ctx.interpreter
176                .halt(revm::interpreter::InstructionResult::StackOverflow);
177        }
178    }
179}
180
181/// SELFBALANCE opcode (0x47).
182const SELFBALANCE_OPCODE: u8 = 0x47;
183
184/// Arbitrum SELFBALANCE: adjusts for poster fee correction if the executing
185/// contract IS the tx sender (edge case: sender calls own address).
186fn arb_selfbalance<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
187    let target = ctx.interpreter.input.target_address();
188
189    let Some(state_load) = ctx.host.balance(target) else {
190        ctx.interpreter.halt_fatal();
191        return;
192    };
193
194    // Apply poster fee correction if the contract being executed is the tx sender
195    let balance = if target == arb_precompiles::get_current_tx_sender() {
196        state_load
197            .data
198            .saturating_sub(arb_precompiles::get_poster_balance_correction())
199    } else {
200        state_load.data
201    };
202
203    if !ctx.interpreter.stack.push(balance) {
204        ctx.interpreter
205            .halt(revm::interpreter::InstructionResult::StackOverflow);
206    }
207}
208
209/// BLOBBASEFEE is not supported on Arbitrum — execution halts.
210fn arb_blob_basefee<WIRE: InterpreterTypes, H: Host + ?Sized>(
211    ctx: InstructionContext<'_, H, WIRE>,
212) {
213    ctx.interpreter.halt(InstructionResult::OpcodeNotFound);
214}
215
216/// Arbitrum SELFDESTRUCT: reverts if the acting account is a Stylus program,
217/// otherwise delegates to the standard EIP-6780 selfdestruct logic.
218fn arb_selfdestruct<WIRE: InterpreterTypes, H: Host + ?Sized>(
219    ctx: InstructionContext<'_, H, WIRE>,
220) {
221    if ctx.interpreter.runtime_flag.is_static() {
222        ctx.interpreter
223            .halt(InstructionResult::StateChangeDuringStaticCall);
224        return;
225    }
226
227    // Stylus programs cannot be self-destructed.
228    let acting_addr = ctx.interpreter.input.target_address();
229    match ctx.host.load_account_code(acting_addr) {
230        Some(code_load) => {
231            if arb_stylus::is_stylus_program(&code_load.data) {
232                ctx.interpreter.halt(InstructionResult::Revert);
233                return;
234            }
235        }
236        None => {
237            ctx.interpreter.halt_fatal();
238            return;
239        }
240    }
241
242    // Standard selfdestruct logic (matching revm's EIP-6780 implementation).
243    // Pop U256 and convert to Address manually (avoids pop_address() which
244    // triggers a ruint 1.17 const eval panic due to U256->Address byte size mismatch).
245    let Some(raw) = ctx.interpreter.stack.pop() else {
246        ctx.interpreter.halt(InstructionResult::StackUnderflow);
247        return;
248    };
249    let target = Address::from_word(alloy_primitives::B256::from(raw.to_be_bytes()));
250
251    let spec = ctx.interpreter.runtime_flag.spec_id();
252    let cold_load_gas = ctx.host.gas_params().selfdestruct_cold_cost();
253    let skip_cold_load = ctx.interpreter.gas.remaining() < cold_load_gas;
254
255    let res = match ctx.host.selfdestruct(acting_addr, target, skip_cold_load) {
256        Ok(res) => res,
257        Err(LoadError::ColdLoadSkipped) => {
258            ctx.interpreter.halt_oog();
259            return;
260        }
261        Err(LoadError::DBError) => {
262            ctx.interpreter.halt_fatal();
263            return;
264        }
265    };
266
267    // EIP-161: State trie clearing.
268    let should_charge_topup = if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
269        res.had_value && !res.target_exists
270    } else {
271        !res.target_exists
272    };
273
274    let gas_cost = ctx
275        .host
276        .gas_params()
277        .selfdestruct_cost(should_charge_topup, res.is_cold);
278    if !ctx.interpreter.gas.record_cost(gas_cost) {
279        ctx.interpreter.halt_oog();
280        return;
281    }
282
283    if !res.previously_destroyed {
284        ctx.interpreter
285            .gas
286            .record_refund(ctx.host.gas_params().selfdestruct_refund());
287    }
288
289    ctx.interpreter.halt(InstructionResult::SelfDestruct);
290}
291
292// ── Stylus page tracking & reentrancy ───────────────────────────────
293//
294// Page tracking and reentrancy state lives in arb_stylus::pages so that
295// both arb-evm (dispatch) and arb-stylus (EvmApi add_pages) can access it.
296
297pub use arb_stylus::pages::{
298    add_stylus_pages, get_stylus_pages, get_stylus_program_count, pop_stylus_program,
299    push_stylus_program, reset_stylus_pages, set_stylus_pages_open,
300};
301
302// ── Stylus storage helpers ───────────────────────────────────────────
303
304use arb_precompiles::storage_slot::{
305    derive_subspace_key, map_slot, map_slot_b256, ARBOS_STATE_ADDRESS, PROGRAMS_DATA_KEY,
306    PROGRAMS_PARAMS_KEY, PROGRAMS_SUBSPACE, ROOT_STORAGE_KEY,
307};
308use arbos::programs::{memory::MemoryModel, params::StylusParams, Program};
309
310/// Read a storage slot from ArbOS state via the journal.
311fn sload_arbos<DB: Database>(journal: &mut revm::Journal<DB>, slot: U256) -> Option<U256> {
312    let _ = journal
313        .inner
314        .load_account(&mut journal.database, ARBOS_STATE_ADDRESS)
315        .ok()?;
316    let result = journal
317        .inner
318        .sload(&mut journal.database, ARBOS_STATE_ADDRESS, slot, false)
319        .ok()?;
320    Some(result.data)
321}
322
323/// Read the StylusParams packed word from storage.
324fn read_params_word<DB: Database>(journal: &mut revm::Journal<DB>) -> Option<[u8; 32]> {
325    let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
326    let params_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_PARAMS_KEY);
327    let slot = map_slot(params_key.as_slice(), 0);
328    sload_arbos(journal, slot).map(|v| v.to_be_bytes::<32>())
329}
330
331/// Read program data word by code hash.
332fn read_program_word<DB: Database>(
333    journal: &mut revm::Journal<DB>,
334    code_hash: B256,
335) -> Option<B256> {
336    let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
337    let data_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_DATA_KEY);
338    let slot = map_slot_b256(data_key.as_slice(), &code_hash);
339    sload_arbos(journal, slot).map(|v| B256::from(v.to_be_bytes::<32>()))
340}
341
342/// Read the activation-time module hash for a Stylus program by code hash.
343/// Read the activation-time module hash stored at programs subspace key [2].
344fn read_module_hash<DB: Database>(
345    journal: &mut revm::Journal<DB>,
346    code_hash: B256,
347) -> Option<B256> {
348    let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
349    let module_hashes_key = derive_subspace_key(programs_key.as_slice(), &[2]);
350    let slot = map_slot_b256(module_hashes_key.as_slice(), &code_hash);
351    sload_arbos(journal, slot).map(|v| B256::from(v.to_be_bytes::<32>()))
352}
353
354/// Parse essential StylusParams fields from the packed storage word.
355fn parse_stylus_params(word: &[u8; 32], arbos_version: u64) -> StylusParams {
356    StylusParams {
357        arbos_version,
358        version: u16::from_be_bytes([word[0], word[1]]),
359        ink_price: (word[2] as u32) << 16 | (word[3] as u32) << 8 | word[4] as u32,
360        max_stack_depth: u32::from_be_bytes([word[5], word[6], word[7], word[8]]),
361        free_pages: u16::from_be_bytes([word[9], word[10]]),
362        page_gas: u16::from_be_bytes([word[11], word[12]]),
363        page_ramp: arbos::programs::params::INITIAL_PAGE_RAMP,
364        page_limit: u16::from_be_bytes([word[13], word[14]]),
365        min_init_gas: word[15],
366        min_cached_init_gas: word[16],
367        init_cost_scalar: word[17],
368        cached_cost_scalar: word[18],
369        expiry_days: u16::from_be_bytes([word[19], word[20]]),
370        keepalive_days: u16::from_be_bytes([word[21], word[22]]),
371        block_cache_size: u16::from_be_bytes([word[23], word[24]]),
372        // These fields span to word 2; not needed for dispatch.
373        max_wasm_size: 0,
374        max_fragment_count: 0,
375    }
376}
377
378/// Compute upfront gas cost for a Stylus call, per `Programs.CallProgram`.
379fn stylus_call_gas_cost(
380    params: &StylusParams,
381    program: &Program,
382    pages_open: u16,
383    pages_ever: u16,
384) -> u64 {
385    let model = MemoryModel::new(params.free_pages, params.page_gas);
386    let mut cost = model.gas_cost(program.footprint, pages_open, pages_ever);
387
388    let cached = program.cached;
389    if cached || program.version > 1 {
390        cost = cost.saturating_add(program.cached_gas(params));
391    }
392    if !cached {
393        cost = cost.saturating_add(program.init_gas(params));
394    }
395    cost
396}
397
398// ── Stylus sub-call trampolines ─────────────────────────────────────
399
400use arb_stylus::evm_api_impl::{SubCallResult, SubCreateResult};
401
402/// Monomorphized trampoline for Stylus sub-calls (CALL/DELEGATECALL/STATICCALL).
403///
404/// This function is created as a concrete `fn(...)` pointer by monomorphizing
405/// generic type parameters at the call site in `execute_stylus_program`.
406/// The `ctx` pointer is cast back to the concrete Context type.
407fn stylus_call_trampoline<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
408    ctx: *mut (),
409    call_type: u8,
410    contract: Address,
411    caller: Address,
412    storage_addr: Address,
413    input: &[u8],
414    gas: u64,
415    value: U256,
416) -> SubCallResult
417where
418    BlockEnv: revm::context::Block,
419    TxEnv: revm::context::Transaction,
420    CfgEnv: revm::context::Cfg,
421    DB: Database,
422{
423    let context = unsafe {
424        &mut *(ctx as *mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>)
425    };
426
427    let is_static = call_type == 2;
428    let is_delegate = call_type == 1;
429
430    // Create a journal checkpoint for the sub-call
431    let checkpoint = context.journaled_state.inner.checkpoint();
432
433    // For CALL with value, transfer ETH
434    if !is_delegate && !value.is_zero() {
435        let transfer_result = context.journaled_state.inner.transfer(
436            &mut context.journaled_state.database,
437            caller,
438            contract,
439            value,
440        );
441        if transfer_result.is_err() {
442            context.journaled_state.inner.checkpoint_revert(checkpoint);
443            return SubCallResult {
444                output: Vec::new(),
445                gas_cost: 0,
446                success: false,
447                refund: 0,
448            };
449        }
450    }
451
452    let code_address = contract;
453    let target_address = storage_addr;
454    let _ = is_delegate;
455
456    let call_scheme = match call_type {
457        0 => CallScheme::Call,
458        1 => CallScheme::DelegateCall,
459        2 => CallScheme::StaticCall,
460        _ => CallScheme::Call,
461    };
462
463    let call_value = if is_delegate {
464        revm::interpreter::CallValue::Apparent(value)
465    } else {
466        revm::interpreter::CallValue::Transfer(value)
467    };
468
469    let sub_inputs = CallInputs {
470        input: CallInput::Bytes(input.to_vec().into()),
471        gas_limit: gas,
472        target_address,
473        bytecode_address: code_address,
474        caller,
475        value: call_value,
476        scheme: call_scheme,
477        is_static,
478        return_memory_offset: 0..0,
479        known_bytecode: None,
480    };
481
482    {
483        arb_precompiles::set_evm_depth(context.journaled_state.inner.depth);
484        let spec: revm::primitives::hardfork::SpecId = context.cfg.spec().into();
485        let mut precompiles =
486            alloy_evm::precompiles::PrecompilesMap::from(revm::handler::EthPrecompiles::new(spec));
487        register_arb_precompiles(&mut precompiles, arb_precompiles::get_arbos_version());
488        let mut arb_map = ArbPrecompilesMap(precompiles);
489        let dispatch_result = <ArbPrecompilesMap as PrecompileProvider<
490            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
491        >>::run(&mut arb_map, context, &sub_inputs);
492
493        match dispatch_result {
494            Ok(Some(result)) => {
495                let success = result.result.is_ok();
496                let output = result.output.to_vec();
497                // On failure (revert, OOG, error) a non-reverting callee has
498                // returned gas left in result.gas, but a precompile that
499                // signalled OOG/PrecompileError leaves `result.gas` untouched
500                // (see alloy-evm precompile runner). Ethereum semantics charge
501                // the caller for all gas forwarded on non-revert failures.
502                let gas_used = if success || matches!(result.result, InstructionResult::Revert) {
503                    gas.saturating_sub(result.gas.remaining())
504                } else {
505                    gas
506                };
507                let refund = if success { result.gas.refunded() } else { 0 };
508                if success {
509                    context.journaled_state.inner.checkpoint_commit();
510                } else {
511                    context.journaled_state.inner.checkpoint_revert(checkpoint);
512                }
513                return SubCallResult {
514                    output,
515                    gas_cost: gas_used,
516                    success,
517                    refund,
518                };
519            }
520            Ok(None) => {}
521            Err(_) => {
522                context.journaled_state.inner.checkpoint_revert(checkpoint);
523                return SubCallResult {
524                    output: Vec::new(),
525                    gas_cost: gas,
526                    success: false,
527                    refund: 0,
528                };
529            }
530        }
531    }
532
533    let bytecode = match context
534        .journaled_state
535        .inner
536        .load_code(&mut context.journaled_state.database, code_address)
537    {
538        Ok(acc) => acc
539            .data
540            .info
541            .code
542            .as_ref()
543            .map(|c| c.original_bytes())
544            .unwrap_or_default(),
545        Err(_) => {
546            context.journaled_state.inner.checkpoint_revert(checkpoint);
547            return SubCallResult {
548                output: Vec::new(),
549                gas_cost: 0,
550                success: false,
551                refund: 0,
552            };
553        }
554    };
555
556    if bytecode.is_empty() {
557        context.journaled_state.inner.checkpoint_commit();
558        return SubCallResult {
559            output: Vec::new(),
560            gas_cost: 0,
561            success: true,
562            refund: 0,
563        };
564    }
565
566    // If the loaded bytecode carries the Stylus discriminant, dispatch it
567    // through the WASM runtime instead of the EVM interpreter.
568    if arb_precompiles::get_arbos_version() >= arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS
569        && arb_stylus::is_stylus_program(&bytecode)
570    {
571        let result = execute_stylus_program(context, &sub_inputs, &bytecode);
572        let success = result.result.is_ok();
573        let output = result.output.to_vec();
574        let gas_used = gas.saturating_sub(result.gas.remaining());
575        let refund = result.gas.refunded();
576        if success {
577            context.journaled_state.inner.checkpoint_commit();
578        } else {
579            context.journaled_state.inner.checkpoint_revert(checkpoint);
580        }
581        return SubCallResult {
582            output,
583            gas_cost: gas_used,
584            success,
585            refund,
586        };
587    }
588
589    let result = run_evm_bytecode(context, &sub_inputs, &bytecode, gas);
590    let success = result.result.is_ok();
591    let output = result.output.to_vec();
592    let gas_used = gas.saturating_sub(result.gas.remaining());
593    let refund = result.gas.refunded();
594    if success {
595        context.journaled_state.inner.checkpoint_commit();
596    } else {
597        context.journaled_state.inner.checkpoint_revert(checkpoint);
598    }
599    SubCallResult {
600        output,
601        gas_cost: gas_used,
602        success,
603        refund,
604    }
605}
606
607/// Monomorphized trampoline for Stylus CREATE/CREATE2 operations.
608fn stylus_create_trampoline<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
609    ctx: *mut (),
610    caller: Address,
611    code: &[u8],
612    gas: u64,
613    endowment: U256,
614    salt: Option<B256>,
615) -> SubCreateResult
616where
617    BlockEnv: revm::context::Block,
618    TxEnv: revm::context::Transaction,
619    CfgEnv: revm::context::Cfg,
620    DB: Database,
621{
622    let context = unsafe {
623        &mut *(ctx as *mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>)
624    };
625
626    let checkpoint = context.journaled_state.inner.checkpoint();
627
628    // Compute CREATE/CREATE2 address
629    let caller_nonce = {
630        let acc = context
631            .journaled_state
632            .inner
633            .load_account(&mut context.journaled_state.database, caller);
634        acc.map(|a| a.data.info.nonce).unwrap_or(0)
635    };
636
637    let created_address = if let Some(salt) = salt {
638        // CREATE2: keccak256(0xff ++ sender ++ salt ++ keccak256(code))
639        let code_hash = alloy_primitives::keccak256(code);
640        let mut buf = Vec::with_capacity(1 + 20 + 32 + 32);
641        buf.push(0xff);
642        buf.extend_from_slice(caller.as_slice());
643        buf.extend_from_slice(salt.as_slice());
644        buf.extend_from_slice(code_hash.as_slice());
645        Address::from_slice(&alloy_primitives::keccak256(&buf)[12..])
646    } else {
647        // CREATE: RLP([sender, nonce])
648        use alloy_rlp::Encodable;
649        let mut rlp_buf = Vec::with_capacity(64);
650        alloy_rlp::Header {
651            list: true,
652            payload_length: caller.length() + caller_nonce.length(),
653        }
654        .encode(&mut rlp_buf);
655        caller.encode(&mut rlp_buf);
656        caller_nonce.encode(&mut rlp_buf);
657        Address::from_slice(&alloy_primitives::keccak256(&rlp_buf)[12..])
658    };
659
660    // Increment caller nonce
661    let _ = context
662        .journaled_state
663        .inner
664        .load_account(&mut context.journaled_state.database, caller);
665    if let Some(acc) = context.journaled_state.inner.state.get_mut(&caller) {
666        acc.info.nonce += 1;
667        context
668            .journaled_state
669            .inner
670            .nonce_bump_journal_entry(caller);
671    }
672
673    // Transfer endowment
674    if !endowment.is_zero()
675        && context
676            .journaled_state
677            .inner
678            .transfer(
679                &mut context.journaled_state.database,
680                caller,
681                created_address,
682                endowment,
683            )
684            .is_err()
685    {
686        context.journaled_state.inner.checkpoint_revert(checkpoint);
687        return SubCreateResult {
688            address: None,
689            output: Vec::new(),
690            gas_cost: gas,
691        };
692    }
693
694    // Run init code as EVM
695    let init_inputs = CallInputs {
696        input: CallInput::Bytes(code.to_vec().into()),
697        gas_limit: gas,
698        target_address: created_address,
699        bytecode_address: created_address,
700        caller,
701        value: revm::interpreter::CallValue::Transfer(endowment),
702        scheme: CallScheme::Call,
703        is_static: false,
704        return_memory_offset: 0..0,
705        known_bytecode: None,
706    };
707
708    let result = run_evm_bytecode(context, &init_inputs, code, gas);
709    let success = result.result.is_ok();
710    let gas_used = gas.saturating_sub(result.gas.remaining());
711
712    if success {
713        // Store the returned bytecode as the contract's code
714        let deployed_code = result.output.to_vec();
715        let code_hash = alloy_primitives::keccak256(&deployed_code);
716        let bytecode = revm::bytecode::Bytecode::new_raw(deployed_code.into());
717        // Ensure the account is loaded into state
718        let _ = context
719            .journaled_state
720            .inner
721            .load_account(&mut context.journaled_state.database, created_address);
722        context
723            .journaled_state
724            .inner
725            .set_code_with_hash(created_address, bytecode, code_hash);
726        context.journaled_state.inner.checkpoint_commit();
727        SubCreateResult {
728            address: Some(created_address),
729            output: Vec::new(), // success doesn't return data
730            gas_cost: gas_used,
731        }
732    } else {
733        let output = result.output.to_vec();
734        context.journaled_state.inner.checkpoint_revert(checkpoint);
735        SubCreateResult {
736            address: None,
737            output, // revert returns data
738            gas_cost: gas_used,
739        }
740    }
741}
742
743/// Run EVM bytecode from a Stylus sub-call.
744///
745/// Creates an interpreter and runs in a loop, dispatching nested CALL/CREATE
746/// actions through the Stylus call trampoline (which in turn uses
747/// ArbPrecompilesMap for precompile/Stylus dispatch).
748fn run_evm_bytecode<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
749    context: &mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
750    inputs: &CallInputs,
751    bytecode: &[u8],
752    gas_limit: u64,
753) -> InterpreterResult
754where
755    BlockEnv: revm::context::Block,
756    TxEnv: revm::context::Transaction,
757    CfgEnv: revm::context::Cfg,
758    DB: Database,
759{
760    use revm::{
761        bytecode::Bytecode,
762        interpreter::{
763            interpreter::{ExtBytecode, InputsImpl},
764            FrameInput, InterpreterAction, SharedMemory,
765        },
766    };
767
768    let code = Bytecode::new_raw(bytecode.to_vec().into());
769    let ext_bytecode = ExtBytecode::new(code);
770
771    let call_value = inputs.value.get();
772    let interp_input = InputsImpl {
773        target_address: inputs.target_address,
774        bytecode_address: Some(inputs.bytecode_address),
775        caller_address: inputs.caller,
776        input: inputs.input.clone(),
777        call_value,
778    };
779
780    let spec = context.cfg.spec();
781
782    let mut interpreter = revm::interpreter::Interpreter::new(
783        SharedMemory::new(),
784        ext_bytecode,
785        interp_input,
786        inputs.is_static,
787        spec.clone().into(),
788        gas_limit,
789    );
790
791    // Install the Arbitrum opcode overrides on every sub-frame. The outer
792    // factory does the same; this path is taken when a Stylus contract
793    // re-enters EVM bytecode and must see identical semantics (notably NUMBER
794    // returning the L1 block number that EIP-712 signatures depend on).
795    type Ctx<B, T, C, D, Ch> = revm::Context<B, T, C, D, revm::Journal<D>, Ch>;
796    let mut instructions = EthInstructions::<
797        EthInterpreter,
798        Ctx<BlockEnv, TxEnv, CfgEnv, DB, Chain>,
799    >::new_mainnet_with_spec(spec.into());
800    instructions.insert_instruction(
801        BLOBBASEFEE_OPCODE,
802        revm::interpreter::Instruction::new(arb_blob_basefee, 2),
803    );
804    instructions.insert_instruction(
805        SELFDESTRUCT_OPCODE,
806        revm::interpreter::Instruction::new(arb_selfdestruct, 5000),
807    );
808    instructions.insert_instruction(
809        NUMBER_OPCODE,
810        revm::interpreter::Instruction::new(arb_number, 2),
811    );
812    instructions.insert_instruction(
813        BLOCKHASH_OPCODE,
814        revm::interpreter::Instruction::new(arb_blockhash, 20),
815    );
816    instructions.insert_instruction(
817        BALANCE_OPCODE,
818        revm::interpreter::Instruction::new(arb_balance, 0),
819    );
820    instructions.insert_instruction(
821        SELFBALANCE_OPCODE,
822        revm::interpreter::Instruction::new(arb_selfbalance, 5),
823    );
824
825    // Run the interpreter in a loop, handling nested calls/creates
826    loop {
827        let action = interpreter.run_plain(&instructions.instruction_table, context);
828
829        match action {
830            InterpreterAction::Return(result) => {
831                return result;
832            }
833            InterpreterAction::NewFrame(FrameInput::Call(sub_call)) => {
834                // Dispatch nested call through our trampoline.
835                // For DELEGATECALL, target_address is the storage context (preserved
836                // from parent), and caller is the msg.sender (preserved). For
837                // CALL/STATICCALL, target_address == bytecode_address.
838                //
839                // Resolve `sub_call.input` against the *interpreter's* SharedMemory.
840                // We can't use `sub_call.input.bytes(context)` because that reads
841                // from `context.local()` — the global context's local memory —
842                // whereas this frame's CALL/STATICCALL was issued against the
843                // private SharedMemory we created at the top of this function.
844                // Reading from the wrong memory returns garbage and silently
845                // corrupts the input to ecrecover, transferFrom, etc.
846                let resolved_input: Bytes = match &sub_call.input {
847                    revm::interpreter::CallInput::Bytes(b) => b.clone(),
848                    revm::interpreter::CallInput::SharedBuffer(range) => {
849                        // The range was computed by call_helpers as
850                        //   range.start = relative_offset + local_memory_offset()
851                        // i.e. an absolute offset into the SharedMemory buffer.
852                        Bytes::from(
853                            interpreter
854                                .memory
855                                .global_slice_range(range.clone())
856                                .to_vec(),
857                        )
858                    }
859                };
860                let bytecode_address = sub_call.bytecode_address;
861                let sub_result = stylus_call_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
862                    context as *mut _ as *mut (),
863                    match sub_call.scheme {
864                        CallScheme::Call | CallScheme::CallCode => 0,
865                        CallScheme::DelegateCall => 1,
866                        CallScheme::StaticCall => 2,
867                    },
868                    bytecode_address,
869                    sub_call.caller,
870                    sub_call.target_address,
871                    &resolved_input,
872                    sub_call.gas_limit,
873                    sub_call.value.get(),
874                );
875
876                // Inject result back into interpreter (matching EthFrame::return_result)
877                let gas_remaining = sub_call.gas_limit.saturating_sub(sub_result.gas_cost);
878                let ins_result = if sub_result.success {
879                    InstructionResult::Return
880                } else {
881                    InstructionResult::Revert
882                };
883
884                let output: Bytes = sub_result.output.into();
885                let returned_len = output.len();
886                let mem_start = sub_call.return_memory_offset.start;
887                let mem_length = sub_call.return_memory_offset.len();
888                let target_len = mem_length.min(returned_len);
889
890                interpreter.return_data.set_buffer(output);
891
892                let item = if ins_result.is_ok() {
893                    U256::from(1)
894                } else {
895                    U256::ZERO
896                };
897                let _ = interpreter.stack.push(item);
898
899                if ins_result.is_ok_or_revert() {
900                    interpreter.gas.erase_cost(gas_remaining);
901                    if target_len > 0 {
902                        interpreter
903                            .memory
904                            .set(mem_start, &interpreter.return_data.buffer()[..target_len]);
905                    }
906                }
907
908                if ins_result.is_ok() {
909                    // Propagate the sub-call's SSTORE refund (EIP-3529) up
910                    // through the Stylus -> EVM bytecode -> Stylus call chain.
911                    // Mirrors revm's `EthFrame::return_result` which also does
912                    // `interpreter.gas.record_refund(out_gas.refunded())` on
913                    // success. Without this, a Stylus contract that goes
914                    // through a Solidity intermediary to reach an inner
915                    // contract that clears storage loses the 4800-gas clearing
916                    // refund (block 55,755,413 tx 2).
917                    interpreter.gas.record_refund(sub_result.refund);
918                }
919            }
920            InterpreterAction::NewFrame(FrameInput::Create(sub_create)) => {
921                // Dispatch create through our trampoline
922                let salt = match sub_create.scheme() {
923                    revm::interpreter::CreateScheme::Create2 { salt } => {
924                        Some(B256::from(salt.to_be_bytes()))
925                    }
926                    _ => None,
927                };
928
929                let sub_result = stylus_create_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
930                    context as *mut _ as *mut (),
931                    sub_create.caller(),
932                    sub_create.init_code(),
933                    sub_create.gas_limit(),
934                    sub_create.value(),
935                    salt,
936                );
937
938                let gas_remaining = sub_create.gas_limit().saturating_sub(sub_result.gas_cost);
939                let created_addr = sub_result.address;
940
941                let ins_result = if created_addr.is_some() {
942                    InstructionResult::Return
943                } else if !sub_result.output.is_empty() {
944                    InstructionResult::Revert
945                } else {
946                    InstructionResult::CreateInitCodeStartingEF00
947                };
948
949                let output: Bytes = sub_result.output.into();
950                interpreter.return_data.set_buffer(output);
951
952                // Push created address or zero
953                let item = match created_addr {
954                    Some(addr) => addr.into_word().into(),
955                    None => U256::ZERO,
956                };
957                let _ = interpreter.stack.push(item);
958
959                if ins_result.is_ok_or_revert() {
960                    interpreter.gas.erase_cost(gas_remaining);
961                }
962            }
963            InterpreterAction::NewFrame(FrameInput::Empty) => {
964                // Should not happen
965                return InterpreterResult::new(
966                    InstructionResult::Revert,
967                    Bytes::new(),
968                    EvmGas::new(0),
969                );
970            }
971        }
972    }
973}
974
975// ── Stylus WASM dispatch ────────────────────────────────────────────
976
977/// Execute a Stylus WASM program by creating a NativeInstance and running it.
978///
979/// Validates the program, computes upfront gas costs (memory pages + init/cached
980/// gas), deducts them, then runs the WASM.
981fn execute_stylus_program<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
982    context: &mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
983    inputs: &CallInputs,
984    bytecode: &[u8],
985) -> InterpreterResult
986where
987    BlockEnv: revm::context::Block,
988    TxEnv: revm::context::Transaction,
989    CfgEnv: revm::context::Cfg,
990    DB: Database,
991{
992    use arbos::programs::types::UserOutcome;
993
994    let zero_gas = || EvmGas::new(0);
995
996    let code_hash = alloy_primitives::keccak256(bytecode);
997    let arbos_version = arb_precompiles::get_arbos_version();
998    let block_timestamp = arb_precompiles::get_block_timestamp();
999
1000    // ── Read and validate program metadata ──────────────────────────
1001    let params_word = match read_params_word(&mut context.journaled_state) {
1002        Some(w) => w,
1003        None => {
1004            tracing::warn!(target: "stylus", "failed to read StylusParams from storage");
1005            return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1006        }
1007    };
1008    let params = parse_stylus_params(&params_word, arbos_version);
1009
1010    let program_word = match read_program_word(&mut context.journaled_state, code_hash) {
1011        Some(w) => w,
1012        None => {
1013            tracing::warn!(target: "stylus", codehash = %code_hash, "failed to read program data");
1014            return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1015        }
1016    };
1017    let program = Program::from_storage(program_word, block_timestamp);
1018
1019    // Validate: program must be activated, correct version, not expired.
1020    if program.version == 0 || program.version != params.version {
1021        tracing::warn!(target: "stylus", codehash = %code_hash, program_ver = program.version, params_ver = params.version, "program version mismatch");
1022        return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1023    }
1024    let expiry_seconds = (params.expiry_days as u64) * 24 * 3600;
1025    if program.age_seconds > expiry_seconds {
1026        tracing::warn!(target: "stylus", codehash = %code_hash, "program expired");
1027        return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1028    }
1029
1030    // ── Compute and deduct upfront gas costs ────────────────────────
1031    let (pages_open, pages_ever) = get_stylus_pages();
1032    // ArbOS v60+: a recent-wasms cache hit counts as cached for pricing.
1033    let recent_wasms_hit = if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_60 {
1034        arb_precompiles::insert_recent_wasm(code_hash)
1035    } else {
1036        false
1037    };
1038    let effective_cached = program.cached || recent_wasms_hit;
1039    let effective_program = if effective_cached != program.cached {
1040        let mut p = program;
1041        p.cached = effective_cached;
1042        p
1043    } else {
1044        program
1045    };
1046    let upfront_cost = stylus_call_gas_cost(&params, &effective_program, pages_open, pages_ever);
1047    let total_gas = inputs.gas_limit;
1048
1049    if total_gas < upfront_cost {
1050        return InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), zero_gas());
1051    }
1052    let gas_for_wasm = total_gas - upfront_cost;
1053
1054    let stylus_config = StylusConfig::new(params.version, params.max_stack_depth, params.ink_price);
1055
1056    // ── Track reentrancy ────────────────────────────────────────────
1057    let target_addr = inputs.target_address;
1058    let is_delegate = matches!(
1059        inputs.scheme,
1060        CallScheme::DelegateCall | CallScheme::CallCode
1061    );
1062    // Only non-delegate-non-callcode calls increment the reentrancy counter.
1063    // Delegate and callcode frames check the counter without bumping it, so an
1064    // actual re-entry into the same storage context reports `reentrant=true`.
1065    let reentrant = if !is_delegate {
1066        push_stylus_program(target_addr)
1067    } else {
1068        get_stylus_program_count(target_addr) > 1
1069    };
1070
1071    // Read the activation-time module hash from storage. This differs from
1072    // code_hash (which is keccak256 of the bytecode); it is the hash of the
1073    // compiled module computed during activateProgram.
1074    let module_hash =
1075        read_module_hash(&mut context.journaled_state, code_hash).unwrap_or(code_hash);
1076
1077    // Build EvmData from the execution context.
1078    let mut evm_data = build_evm_data(context, inputs);
1079    evm_data.reentrant = reentrant as u32;
1080    evm_data.cached = effective_program.cached;
1081    evm_data.module_hash = module_hash;
1082
1083    // Track pages — add this program's footprint.
1084    let (prev_open, _prev_ever) = add_stylus_pages(program.footprint);
1085
1086    // Create the type-erased StylusEvmApi bridge.
1087    let journal_ptr = &mut context.journaled_state as *mut revm::Journal<DB>;
1088    let is_static = inputs.is_static || matches!(inputs.scheme, CallScheme::StaticCall);
1089    let ctx_ptr = context as *mut _ as *mut ();
1090    let caller = inputs.caller;
1091    let call_value = inputs.value.get();
1092    let evm_api = unsafe {
1093        StylusEvmApi::new(
1094            journal_ptr,
1095            target_addr,
1096            caller,
1097            call_value,
1098            is_static,
1099            params.free_pages,
1100            params.page_gas,
1101            arbos_version,
1102            ctx_ptr,
1103            Some(stylus_call_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>),
1104            Some(stylus_create_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>),
1105        )
1106    };
1107
1108    // Try the module cache first; compile from WASM on miss and populate cache.
1109    let long_term_tag = if program.cached { 1u32 } else { 0u32 };
1110    let mut instance = if let Some((module, store)) =
1111        arb_stylus::cache::InitCache::get(code_hash, params.version, long_term_tag, false)
1112    {
1113        let compile = arb_stylus::CompileConfig::version(params.version, false);
1114        let env = arb_stylus::env::WasmEnv::new(compile, Some(stylus_config), evm_api, evm_data);
1115        match arb_stylus::NativeInstance::from_module(module, store, env) {
1116            Ok(inst) => inst,
1117            Err(e) => {
1118                tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "failed from cached module");
1119                set_stylus_pages_open(prev_open);
1120                if !is_delegate {
1121                    pop_stylus_program(target_addr);
1122                }
1123                return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1124            }
1125        }
1126    } else {
1127        let decompressed = match arb_stylus::decompress_wasm(bytecode) {
1128            Ok(w) => w,
1129            Err(e) => {
1130                tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "WASM decompression failed");
1131                set_stylus_pages_open(prev_open);
1132                if !is_delegate {
1133                    pop_stylus_program(target_addr);
1134                }
1135                return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1136            }
1137        };
1138        let compile = arb_stylus::CompileConfig::version(params.version, false);
1139        match arb_stylus::NativeInstance::from_bytes(
1140            &decompressed,
1141            evm_api,
1142            evm_data,
1143            &compile,
1144            stylus_config,
1145        ) {
1146            Ok(inst) => inst,
1147            Err(e) => {
1148                tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "failed to compile WASM");
1149                set_stylus_pages_open(prev_open);
1150                if !is_delegate {
1151                    pop_stylus_program(target_addr);
1152                }
1153                return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1154            }
1155        }
1156    };
1157
1158    // Convert EVM gas (after upfront deduction) to ink.
1159    let ink = stylus_config.pricing.gas_to_ink(StylusGas(gas_for_wasm));
1160
1161    // Get calldata from CallInput enum. SharedBuffer references parent's
1162    // memory and must be resolved via the context.
1163    let calldata_owned: Bytes = inputs.input.bytes(context);
1164    let calldata: &[u8] = &calldata_owned;
1165    let outcome = match instance.run_main(calldata, stylus_config, ink) {
1166        Ok(outcome) => outcome,
1167        Err(e) => {
1168            tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "WASM execution failed");
1169            set_stylus_pages_open(prev_open);
1170            if !is_delegate {
1171                pop_stylus_program(target_addr);
1172            }
1173            return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1174        }
1175    };
1176
1177    // Restore page count and pop reentrancy.
1178    set_stylus_pages_open(prev_open);
1179    if !is_delegate {
1180        pop_stylus_program(target_addr);
1181    }
1182
1183    // Convert remaining ink back to gas.
1184    let ink_left = match instance.ink_left() {
1185        arb_stylus::MachineMeter::Ready(ink_val) => ink_val,
1186        arb_stylus::MachineMeter::Exhausted => arb_stylus::Ink(0),
1187    };
1188    let gas_left = stylus_config.pricing.ink_to_gas(ink_left).0;
1189
1190    // Return data cost parity with EVM (ArbOS >= StylusFixes).
1191    let output: Bytes = instance.env().outs.clone().into();
1192    let gas_left = if !output.is_empty()
1193        && arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_FIXES
1194    {
1195        let evm_cost = arbos::programs::types::evm_memory_cost(output.len() as u64);
1196        if total_gas < evm_cost {
1197            0
1198        } else {
1199            gas_left.min(total_gas - evm_cost)
1200        }
1201    } else {
1202        gas_left
1203    };
1204
1205    let mut gas_result = EvmGas::new(gas_left);
1206    // Propagate SSTORE refunds from Stylus flush to the EVM gas accounting.
1207    let sstore_refund = instance.env().evm_api.sstore_refund();
1208    if sstore_refund != 0 {
1209        gas_result.record_refund(sstore_refund);
1210    }
1211
1212    // Map UserOutcome to InterpreterResult.
1213    match outcome {
1214        UserOutcome::Success => {
1215            InterpreterResult::new(InstructionResult::Return, output, gas_result)
1216        }
1217        UserOutcome::Revert => {
1218            InterpreterResult::new(InstructionResult::Revert, output, gas_result)
1219        }
1220        UserOutcome::OutOfInk => {
1221            InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), zero_gas())
1222        }
1223        UserOutcome::OutOfStack => {
1224            InterpreterResult::new(InstructionResult::CallTooDeep, Bytes::new(), zero_gas())
1225        }
1226        UserOutcome::Failure => {
1227            InterpreterResult::new(InstructionResult::Revert, Bytes::new(), gas_result)
1228        }
1229    }
1230}
1231
1232/// Build [`EvmData`] from the current execution context.
1233fn build_evm_data<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
1234    context: &revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1235    inputs: &CallInputs,
1236) -> EvmData
1237where
1238    BlockEnv: revm::context::Block,
1239    TxEnv: revm::context::Transaction,
1240    CfgEnv: revm::context::Cfg,
1241    DB: Database,
1242{
1243    let basefee = U256::from(context.block.basefee());
1244    let gas_price = U256::from(context.tx.gas_price());
1245    let value = inputs.value.get();
1246
1247    // Stylus's block.number is the L1 block number, not the L2 block number.
1248    let l1_block_number = arb_precompiles::get_l1_block_number_for_evm();
1249
1250    EvmData {
1251        arbos_version: arb_precompiles::get_arbos_version(),
1252        block_basefee: B256::from(basefee.to_be_bytes()),
1253        chain_id: context.cfg.chain_id(),
1254        block_coinbase: context.block.beneficiary(),
1255        block_gas_limit: context.block.gas_limit(),
1256        block_number: l1_block_number,
1257        block_timestamp: context.block.timestamp().saturating_to(),
1258        contract_address: inputs.target_address,
1259        module_hash: alloy_primitives::keccak256(b""),
1260        msg_sender: inputs.caller,
1261        msg_value: B256::from(value.to_be_bytes()),
1262        tx_gas_price: B256::from(gas_price.to_be_bytes()),
1263        tx_origin: context.tx.caller(),
1264        reentrant: 0,
1265        cached: false,
1266        tracing: false,
1267    }
1268}
1269
1270// ── Stylus frame-level intercept ──────────────────────────────────
1271
1272/// Check if a `FrameInit` targets a Stylus WASM program via `known_bytecode`.
1273fn is_stylus_call(frame_init: &FrameInit) -> Option<Bytes> {
1274    if arb_precompiles::get_arbos_version() < arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS {
1275        return None;
1276    }
1277    if let FrameInput::Call(ref inputs) = frame_init.frame_input {
1278        if let Some((_, ref code)) = inputs.known_bytecode {
1279            let raw = code.original_bytes();
1280            if arb_stylus::is_stylus_program(&raw) {
1281                return Some(raw);
1282            }
1283        }
1284    }
1285    None
1286}
1287
1288/// Execute a Stylus call and return the appropriate `FrameResult`, handling
1289/// journal checkpoint commit/revert.
1290fn execute_stylus_call_concrete<DB: Database>(
1291    ctx: &mut EthEvmContext<DB>,
1292    inputs: &CallInputs,
1293    bytecode: &[u8],
1294    checkpoint: revm::context_interface::journaled_state::JournalCheckpoint,
1295) -> FrameResult {
1296    // Handle value transfer for non-delegate calls (matches EthFrame::make_call_frame).
1297    if let revm::interpreter::CallValue::Transfer(value) = inputs.value {
1298        if let Some(i) =
1299            ctx.journal_mut()
1300                .transfer_loaded(inputs.caller, inputs.target_address, value)
1301        {
1302            let gas = EvmGas::new(inputs.gas_limit);
1303            ctx.journaled_state.inner.checkpoint_revert(checkpoint);
1304            return FrameResult::Call(CallOutcome {
1305                result: InterpreterResult::new(i.into(), Bytes::new(), gas),
1306                memory_offset: inputs.return_memory_offset.clone(),
1307                was_precompile_called: false,
1308                precompile_call_logs: Vec::new(),
1309            });
1310        }
1311    }
1312
1313    let result = execute_stylus_program(ctx, inputs, bytecode);
1314
1315    if result.result.is_ok() {
1316        ctx.journaled_state.inner.checkpoint_commit();
1317    } else {
1318        ctx.journaled_state.inner.checkpoint_revert(checkpoint);
1319    }
1320    FrameResult::Call(CallOutcome {
1321        result,
1322        memory_offset: inputs.return_memory_offset.clone(),
1323        was_precompile_called: false,
1324        precompile_call_logs: Vec::new(),
1325    })
1326}
1327
1328// ── Depth-tracking precompile provider ─────────────────────────────
1329
1330/// Wraps [`PrecompilesMap`] to set the thread-local EVM call depth before
1331/// each precompile invocation. The depth is read from revm's journal, which
1332/// mirrors the `evm.Depth()` counter used by `ArbSys.isTopLevelCall`.
1333#[derive(Clone, Debug)]
1334pub struct ArbPrecompilesMap(pub PrecompilesMap);
1335
1336impl<BlockEnv, TxEnv, CfgEnv, DB, Chain>
1337    PrecompileProvider<revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>>
1338    for ArbPrecompilesMap
1339where
1340    BlockEnv: revm::context::Block,
1341    TxEnv: revm::context::Transaction,
1342    CfgEnv: revm::context::Cfg,
1343    DB: Database,
1344{
1345    type Output = InterpreterResult;
1346
1347    fn set_spec(&mut self, spec: CfgEnv::Spec) -> bool {
1348        <PrecompilesMap as PrecompileProvider<
1349            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1350        >>::set_spec(&mut self.0, spec)
1351    }
1352
1353    fn run(
1354        &mut self,
1355        context: &mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1356        inputs: &CallInputs,
1357    ) -> Result<Option<Self::Output>, String> {
1358        // Sync the thread-local depth from revm's journal before the precompile runs.
1359        arb_precompiles::set_evm_depth(context.journaled_state.inner.depth);
1360
1361        // Check precompiles first.
1362        if let result @ Some(_) = <PrecompilesMap as PrecompileProvider<
1363            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1364        >>::run(&mut self.0, context, inputs)?
1365        {
1366            return Ok(result);
1367        }
1368
1369        // Check for Stylus WASM programs (active at ArbOS v31+).
1370        let arbos_version = arb_precompiles::get_arbos_version();
1371        if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS {
1372            // Use known_bytecode from CallInputs if available (already loaded by
1373            // revm's CALL handler), otherwise load from journal.
1374            let bytecode = inputs
1375                .known_bytecode
1376                .as_ref()
1377                .map(|(_, code)| code.original_bytes())
1378                .or_else(|| {
1379                    context
1380                        .journaled_state
1381                        .inner
1382                        .load_code(
1383                            &mut context.journaled_state.database,
1384                            inputs.bytecode_address,
1385                        )
1386                        .ok()
1387                        .and_then(|acc| acc.data.info.code.as_ref().map(|c| c.original_bytes()))
1388                });
1389
1390            if let Some(bytecode) = bytecode {
1391                if arb_stylus::is_stylus_program(&bytecode) {
1392                    return Ok(Some(execute_stylus_program(context, inputs, &bytecode)));
1393                }
1394            }
1395        }
1396
1397        Ok(None)
1398    }
1399
1400    fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
1401        <PrecompilesMap as PrecompileProvider<
1402            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1403        >>::warm_addresses(&self.0)
1404    }
1405
1406    fn contains(&self, address: &Address) -> bool {
1407        <PrecompilesMap as PrecompileProvider<
1408            revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1409        >>::contains(&self.0, address)
1410    }
1411}
1412
1413// ── ArbEvm ─────────────────────────────────────────────────────────
1414
1415type InnerRevmEvm<DB, I> = RevmEvm<
1416    EthEvmContext<DB>,
1417    I,
1418    EthInstructions<EthInterpreter, EthEvmContext<DB>>,
1419    ArbPrecompilesMap,
1420    EthFrame,
1421>;
1422
1423/// Arbitrum EVM with frame-level Stylus dispatch and custom opcodes.
1424///
1425/// Implements [`EvmTr`] directly to intercept Stylus WASM calls in
1426/// `frame_init` before they reach the precompile provider.
1427pub struct ArbEvm<DB: Database, I> {
1428    inner: InnerRevmEvm<DB, I>,
1429    inspect: bool,
1430}
1431
1432impl<DB, I> ArbEvm<DB, I>
1433where
1434    DB: Database,
1435{
1436    pub fn new(inner: InnerRevmEvm<DB, I>, inspect: bool) -> Self {
1437        Self { inner, inspect }
1438    }
1439
1440    pub fn into_inner(self) -> InnerRevmEvm<DB, I> {
1441        self.inner
1442    }
1443
1444    pub fn ctx(&self) -> &EthEvmContext<DB> {
1445        &self.inner.ctx
1446    }
1447
1448    pub fn ctx_mut(&mut self) -> &mut EthEvmContext<DB> {
1449        &mut self.inner.ctx
1450    }
1451
1452    pub fn precompiles_mut(&mut self) -> &mut ArbPrecompilesMap {
1453        &mut self.inner.precompiles
1454    }
1455}
1456
1457impl<DB: Database, I> core::ops::Deref for ArbEvm<DB, I> {
1458    type Target = EthEvmContext<DB>;
1459    fn deref(&self) -> &Self::Target {
1460        &self.inner.ctx
1461    }
1462}
1463
1464impl<DB: Database, I> core::ops::DerefMut for ArbEvm<DB, I> {
1465    fn deref_mut(&mut self) -> &mut Self::Target {
1466        &mut self.inner.ctx
1467    }
1468}
1469
1470// ── EvmTr for ArbEvm ─────────────────────────────────────────────
1471
1472impl<DB, I> EvmTr for ArbEvm<DB, I>
1473where
1474    DB: Database,
1475    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
1476{
1477    type Context = EthEvmContext<DB>;
1478    type Instructions = EthInstructions<EthInterpreter, EthEvmContext<DB>>;
1479    type Precompiles = ArbPrecompilesMap;
1480    type Frame = EthFrame<EthInterpreter>;
1481
1482    #[inline]
1483    fn all(
1484        &self,
1485    ) -> (
1486        &Self::Context,
1487        &Self::Instructions,
1488        &Self::Precompiles,
1489        &FrameStack<Self::Frame>,
1490    ) {
1491        self.inner.all()
1492    }
1493
1494    #[inline]
1495    fn all_mut(
1496        &mut self,
1497    ) -> (
1498        &mut Self::Context,
1499        &mut Self::Instructions,
1500        &mut Self::Precompiles,
1501        &mut FrameStack<Self::Frame>,
1502    ) {
1503        self.inner.all_mut()
1504    }
1505
1506    #[inline]
1507    fn frame_init(
1508        &mut self,
1509        frame_input: FrameInit,
1510    ) -> Result<
1511        ItemOrResult<&mut Self::Frame, FrameResult>,
1512        revm::handler::evm::ContextDbError<Self::Context>,
1513    > {
1514        // Track msg.sender per frame for ArbSys precompiles.
1515        let pushed_caller = match &frame_input.frame_input {
1516            FrameInput::Call(inputs) => {
1517                arb_precompiles::push_caller_frame(inputs.caller);
1518                true
1519            }
1520            FrameInput::Create(inputs) => {
1521                arb_precompiles::push_caller_frame(inputs.caller());
1522                true
1523            }
1524            _ => false,
1525        };
1526
1527        // Intercept Stylus WASM calls before they reach EthFrame/precompiles.
1528        if let Some(bytecode) = is_stylus_call(&frame_input) {
1529            if let FrameInput::Call(ref inputs) = frame_input.frame_input {
1530                if frame_input.depth > revm::primitives::constants::CALL_STACK_LIMIT as usize {
1531                    let gas = EvmGas::new(inputs.gas_limit);
1532                    if pushed_caller {
1533                        arb_precompiles::pop_caller_frame();
1534                    }
1535                    return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
1536                        result: InterpreterResult::new(
1537                            InstructionResult::CallTooDeep,
1538                            Bytes::new(),
1539                            gas,
1540                        ),
1541                        memory_offset: inputs.return_memory_offset.clone(),
1542                        was_precompile_called: false,
1543                        precompile_call_logs: Vec::new(),
1544                    })));
1545                }
1546                let checkpoint = self.inner.ctx.journal_mut().checkpoint();
1547                let result = execute_stylus_call_concrete(
1548                    &mut self.inner.ctx,
1549                    inputs,
1550                    &bytecode,
1551                    checkpoint,
1552                );
1553                if pushed_caller {
1554                    arb_precompiles::pop_caller_frame();
1555                }
1556                return Ok(ItemOrResult::Result(result));
1557            }
1558        }
1559
1560        // Non-Stylus: delegate. Pop happens in frame_return_result.
1561        self.inner.frame_init(frame_input)
1562    }
1563
1564    #[inline]
1565    fn frame_run(
1566        &mut self,
1567    ) -> Result<
1568        ItemOrResult<FrameInit, FrameResult>,
1569        revm::handler::evm::ContextDbError<Self::Context>,
1570    > {
1571        self.inner.frame_run()
1572    }
1573
1574    #[inline]
1575    fn frame_return_result(
1576        &mut self,
1577        result: FrameResult,
1578    ) -> Result<Option<FrameResult>, revm::handler::evm::ContextDbError<Self::Context>> {
1579        // Pop the caller pushed by frame_init for this frame.
1580        arb_precompiles::pop_caller_frame();
1581        self.inner.frame_return_result(result)
1582    }
1583}
1584
1585// ── ExecuteEvm for ArbEvm ────────────────────────────────────────
1586
1587impl<DB, I> ExecuteEvm for ArbEvm<DB, I>
1588where
1589    DB: Database,
1590    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
1591{
1592    type ExecutionResult = ExecutionResult<HaltReason>;
1593    type State = revm::state::EvmState;
1594    type Error = EVMError<<DB as revm::Database>::Error, InvalidTransaction>;
1595    type Tx = revm::context::TxEnv;
1596    type Block = revm::context::BlockEnv;
1597
1598    #[inline]
1599    fn transact_one(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
1600        self.inner.ctx.set_tx(tx);
1601        MainnetHandler::default().run(self)
1602    }
1603
1604    #[inline]
1605    fn finalize(&mut self) -> Self::State {
1606        self.inner.ctx.journal_mut().finalize()
1607    }
1608
1609    #[inline]
1610    fn set_block(&mut self, block: Self::Block) {
1611        self.inner.ctx.set_block(block);
1612    }
1613
1614    #[inline]
1615    fn replay(&mut self) -> Result<ResultAndState<HaltReason>, Self::Error> {
1616        MainnetHandler::default().run(self).map(|result| {
1617            let state = self.finalize();
1618            ResultAndState::new(result, state)
1619        })
1620    }
1621}
1622
1623// ── SystemCallEvm for ArbEvm ─────────────────────────────────────
1624
1625impl<DB, I> SystemCallEvm for ArbEvm<DB, I>
1626where
1627    DB: Database,
1628    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
1629{
1630    fn system_call_one_with_caller(
1631        &mut self,
1632        caller: Address,
1633        system_contract_address: Address,
1634        data: Bytes,
1635    ) -> Result<Self::ExecutionResult, Self::Error> {
1636        use revm::handler::system_call::SystemCallTx;
1637        self.inner
1638            .ctx
1639            .set_tx(revm::context::TxEnv::new_system_tx_with_caller(
1640                caller,
1641                system_contract_address,
1642                data,
1643            ));
1644        MainnetHandler::default().run_system_call(self)
1645    }
1646}
1647
1648// ── InspectorEvmTr for ArbEvm ────────────────────────────────────
1649
1650impl<DB, I> revm::inspector::InspectorEvmTr for ArbEvm<DB, I>
1651where
1652    DB: Database,
1653    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
1654    revm::Journal<DB>: revm::inspector::JournalExt,
1655{
1656    type Inspector = I;
1657
1658    fn all_inspector(
1659        &self,
1660    ) -> (
1661        &Self::Context,
1662        &Self::Instructions,
1663        &Self::Precompiles,
1664        &FrameStack<Self::Frame>,
1665        &Self::Inspector,
1666    ) {
1667        let (ctx, inst, pre, fs) = self.inner.all();
1668        (ctx, inst, pre, fs, &self.inner.inspector)
1669    }
1670
1671    fn all_mut_inspector(
1672        &mut self,
1673    ) -> (
1674        &mut Self::Context,
1675        &mut Self::Instructions,
1676        &mut Self::Precompiles,
1677        &mut FrameStack<Self::Frame>,
1678        &mut Self::Inspector,
1679    ) {
1680        (
1681            &mut self.inner.ctx,
1682            &mut self.inner.instruction,
1683            &mut self.inner.precompiles,
1684            &mut self.inner.frame_stack,
1685            &mut self.inner.inspector,
1686        )
1687    }
1688}
1689
1690// ── InspectEvm for ArbEvm ────────────────────────────────────────
1691
1692impl<DB, I> InspectEvm for ArbEvm<DB, I>
1693where
1694    DB: Database,
1695    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
1696    revm::Journal<DB>: revm::inspector::JournalExt,
1697{
1698    type Inspector = I;
1699
1700    fn set_inspector(&mut self, inspector: Self::Inspector) {
1701        self.inner.inspector = inspector;
1702    }
1703
1704    fn inspect_one_tx(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
1705        self.inner.ctx.set_tx(tx);
1706        MainnetHandler::default().inspect_run(self)
1707    }
1708}
1709
1710// ── alloy_evm::Evm for ArbEvm ───────────────────────────────────
1711
1712impl<DB, I> Evm for ArbEvm<DB, I>
1713where
1714    DB: Database,
1715    I: Inspector<EthEvmContext<DB>, EthInterpreter>,
1716    revm::Journal<DB>: revm::inspector::JournalExt,
1717{
1718    type DB = DB;
1719    type Tx = ArbTransaction;
1720    type Error = EVMError<<DB as revm::Database>::Error>;
1721    type HaltReason = HaltReason;
1722    type Spec = SpecId;
1723    type Precompiles = PrecompilesMap;
1724    type Inspector = I;
1725    type BlockEnv = revm::context::BlockEnv;
1726
1727    fn block(&self) -> &revm::context::BlockEnv {
1728        &self.inner.ctx.block
1729    }
1730
1731    fn chain_id(&self) -> u64 {
1732        self.inner.ctx.cfg.chain_id
1733    }
1734
1735    fn transact_raw(
1736        &mut self,
1737        tx: Self::Tx,
1738    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
1739        if self.inspect {
1740            InspectEvm::inspect_tx(self, tx.into_inner())
1741        } else {
1742            ExecuteEvm::transact(self, tx.into_inner())
1743        }
1744    }
1745
1746    fn transact_system_call(
1747        &mut self,
1748        caller: Address,
1749        contract: Address,
1750        data: Bytes,
1751    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
1752        SystemCallEvm::system_call_with_caller(self, caller, contract, data)
1753    }
1754
1755    fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) {
1756        let revm::Context {
1757            block: block_env,
1758            cfg: cfg_env,
1759            journaled_state,
1760            ..
1761        } = self.inner.ctx;
1762        (journaled_state.database, EvmEnv { block_env, cfg_env })
1763    }
1764
1765    fn set_inspector_enabled(&mut self, enabled: bool) {
1766        self.inspect = enabled;
1767    }
1768
1769    fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
1770        (
1771            &self.inner.ctx.journaled_state.database,
1772            &self.inner.inspector,
1773            &self.inner.precompiles.0,
1774        )
1775    }
1776
1777    fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
1778        (
1779            &mut self.inner.ctx.journaled_state.database,
1780            &mut self.inner.inspector,
1781            &mut self.inner.precompiles.0,
1782        )
1783    }
1784}
1785
1786// ── ArbEvmFactory ──────────────────────────────────────────────────
1787
1788/// Factory for creating Arbitrum EVM instances with custom precompiles.
1789#[derive(Default, Debug, Clone, Copy)]
1790pub struct ArbEvmFactory(pub alloy_evm::EthEvmFactory);
1791
1792impl ArbEvmFactory {
1793    pub fn new() -> Self {
1794        Self::default()
1795    }
1796}
1797
1798fn build_arb_evm<DB: Database, I>(
1799    inner: RevmEvm<
1800        EthEvmContext<DB>,
1801        I,
1802        EthInstructions<EthInterpreter, EthEvmContext<DB>>,
1803        PrecompilesMap,
1804        EthFrame,
1805    >,
1806    inspect: bool,
1807) -> ArbEvm<DB, I> {
1808    let RevmEvm {
1809        ctx,
1810        inspector,
1811        mut instruction,
1812        mut precompiles,
1813        frame_stack: _,
1814    } = inner;
1815
1816    instruction.insert_instruction(
1817        BLOBBASEFEE_OPCODE,
1818        revm::interpreter::Instruction::new(arb_blob_basefee, 2),
1819    );
1820    instruction.insert_instruction(
1821        SELFDESTRUCT_OPCODE,
1822        revm::interpreter::Instruction::new(arb_selfdestruct, 5000),
1823    );
1824    instruction.insert_instruction(
1825        NUMBER_OPCODE,
1826        revm::interpreter::Instruction::new(arb_number, 2),
1827    );
1828    instruction.insert_instruction(
1829        BLOCKHASH_OPCODE,
1830        revm::interpreter::Instruction::new(arb_blockhash, 20),
1831    );
1832    instruction.insert_instruction(
1833        BALANCE_OPCODE,
1834        revm::interpreter::Instruction::new(arb_balance, 0),
1835    );
1836    instruction.insert_instruction(
1837        SELFBALANCE_OPCODE,
1838        revm::interpreter::Instruction::new(arb_selfbalance, 5),
1839    );
1840    register_arb_precompiles(&mut precompiles, arb_precompiles::get_arbos_version());
1841    let arb_precompiles = ArbPrecompilesMap(precompiles);
1842
1843    let revm_evm = RevmEvm::new_with_inspector(ctx, inspector, instruction, arb_precompiles);
1844    ArbEvm::new(revm_evm, inspect)
1845}
1846
1847impl EvmFactory for ArbEvmFactory {
1848    type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> = ArbEvm<DB, I>;
1849    type Context<DB: Database> = EthEvmContext<DB>;
1850    type Tx = ArbTransaction;
1851    type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
1852    type HaltReason = HaltReason;
1853    type Spec = SpecId;
1854    type Precompiles = PrecompilesMap;
1855    type BlockEnv = revm::context::BlockEnv;
1856
1857    fn create_evm<DB: Database>(
1858        &self,
1859        db: DB,
1860        input: EvmEnv<Self::Spec>,
1861    ) -> Self::Evm<DB, NoOpInspector> {
1862        let eth_evm = self.0.create_evm(db, input);
1863        build_arb_evm(eth_evm.into_inner(), false)
1864    }
1865
1866    fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>, EthInterpreter>>(
1867        &self,
1868        db: DB,
1869        input: EvmEnv<Self::Spec>,
1870        inspector: I,
1871    ) -> Self::Evm<DB, I> {
1872        let eth_evm = self.0.create_evm_with_inspector(db, input, inspector);
1873        build_arb_evm(eth_evm.into_inner(), true)
1874    }
1875}