Skip to main content

revm_handler/
frame.rs

1use crate::{
2    evm::FrameTr, item_or_result::FrameInitOrResult, precompile_provider::PrecompileProvider,
3    CallFrame, CreateFrame, FrameData, FrameResult, ItemOrResult,
4};
5use context::{result::FromStringError, LocalContextTr};
6use context_interface::{
7    context::{take_error, ContextError},
8    journaled_state::{account::JournaledAccountTr, JournalCheckpoint, JournalTr},
9    local::{FrameToken, OutFrame},
10    Cfg, ContextTr, Database,
11};
12use core::cmp::min;
13use derive_where::derive_where;
14use interpreter::{
15    interpreter::{EthInterpreter, ExtBytecode},
16    interpreter_action::FrameInit,
17    interpreter_types::ReturnData,
18    CallInput, CallInputs, CallOutcome, CallValue, CreateInputs, CreateOutcome, CreateScheme,
19    FrameInput, Gas, InputsImpl, InstructionResult, Interpreter, InterpreterAction,
20    InterpreterResult, InterpreterTypes, SharedMemory,
21};
22use primitives::{
23    constants::CALL_STACK_LIMIT,
24    hardfork::SpecId::{self, HOMESTEAD, LONDON, SPURIOUS_DRAGON},
25    Address, Bytes, U256,
26};
27use state::Bytecode;
28use std::{borrow::ToOwned, boxed::Box, vec::Vec};
29
30/// Frame implementation for Ethereum.
31#[derive_where(Clone, Debug; IW,
32    <IW as InterpreterTypes>::Stack,
33    <IW as InterpreterTypes>::Memory,
34    <IW as InterpreterTypes>::Bytecode,
35    <IW as InterpreterTypes>::ReturnData,
36    <IW as InterpreterTypes>::Input,
37    <IW as InterpreterTypes>::RuntimeFlag,
38    <IW as InterpreterTypes>::Extend,
39)]
40pub struct EthFrame<IW: InterpreterTypes = EthInterpreter> {
41    /// Frame-specific data (Call, Create, or EOFCreate).
42    pub data: FrameData,
43    /// Input data for the frame.
44    pub input: FrameInput,
45    /// Current call depth in the execution stack.
46    pub depth: usize,
47    /// Journal checkpoint for state reversion.
48    pub checkpoint: JournalCheckpoint,
49    /// Interpreter instance for executing bytecode.
50    pub interpreter: Interpreter<IW>,
51    /// Whether the frame has been finished its execution.
52    /// Frame is considered finished if it has been called and returned a result.
53    pub is_finished: bool,
54}
55
56impl<IT: InterpreterTypes> FrameTr for EthFrame<IT> {
57    type FrameResult = FrameResult;
58    type FrameInit = FrameInit;
59}
60
61impl Default for EthFrame<EthInterpreter> {
62    fn default() -> Self {
63        Self::do_default(Interpreter::default())
64    }
65}
66
67impl EthFrame<EthInterpreter> {
68    /// Creates an new invalid [`EthFrame`].
69    pub fn invalid() -> Self {
70        Self::do_default(Interpreter::invalid())
71    }
72
73    fn do_default(interpreter: Interpreter<EthInterpreter>) -> Self {
74        Self {
75            data: FrameData::Call(CallFrame {
76                return_memory_range: 0..0,
77            }),
78            input: FrameInput::Empty,
79            depth: 0,
80            checkpoint: JournalCheckpoint::default(),
81            interpreter,
82            is_finished: false,
83        }
84    }
85
86    /// Returns true if the frame has finished execution.
87    pub const fn is_finished(&self) -> bool {
88        self.is_finished
89    }
90
91    /// Sets the finished state of the frame.
92    pub const fn set_finished(&mut self, finished: bool) {
93        self.is_finished = finished;
94    }
95}
96
97/// Type alias for database errors from a context.
98pub type ContextTrDbError<CTX> = <<CTX as ContextTr>::Db as Database>::Error;
99
100impl EthFrame<EthInterpreter> {
101    /// Clear and initialize a frame.
102    #[expect(clippy::too_many_arguments)]
103    #[inline(always)]
104    pub fn clear(
105        &mut self,
106        data: FrameData,
107        input: FrameInput,
108        depth: usize,
109        memory: SharedMemory,
110        bytecode: ExtBytecode,
111        inputs: InputsImpl,
112        is_static: bool,
113        spec_id: SpecId,
114        gas_limit: u64,
115        reservoir_remaining_gas: u64,
116        checkpoint: JournalCheckpoint,
117    ) {
118        let Self {
119            data: data_ref,
120            input: input_ref,
121            depth: depth_ref,
122            interpreter,
123            checkpoint: checkpoint_ref,
124            is_finished: is_finished_ref,
125        } = self;
126        *data_ref = data;
127        *input_ref = input;
128        *depth_ref = depth;
129        *is_finished_ref = false;
130        interpreter.clear(
131            memory,
132            bytecode,
133            inputs,
134            is_static,
135            spec_id,
136            gas_limit,
137            reservoir_remaining_gas,
138        );
139        *checkpoint_ref = checkpoint;
140    }
141
142    /// Make call frame
143    #[inline]
144    pub fn make_call_frame<
145        CTX: ContextTr,
146        PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
147        ERROR: From<ContextTrDbError<CTX>> + FromStringError,
148    >(
149        mut this: OutFrame<'_, Self>,
150        ctx: &mut CTX,
151        precompiles: &mut PRECOMPILES,
152        depth: usize,
153        memory: SharedMemory,
154        inputs: Box<CallInputs>,
155    ) -> Result<ItemOrResult<FrameToken, FrameResult>, ERROR> {
156        let reservoir_remaining_gas = inputs.reservoir;
157        let charged_new_account_state_gas = inputs.charged_new_account_state_gas;
158        let gas =
159            Gas::new_with_regular_gas_and_reservoir(inputs.gas_limit, reservoir_remaining_gas);
160        let return_result = |instruction_result: InstructionResult| {
161            Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
162                result: InterpreterResult {
163                    result: instruction_result,
164                    gas,
165                    output: Bytes::new(),
166                },
167                memory_offset: inputs.return_memory_offset.clone(),
168                was_precompile_called: false,
169                precompile_call_logs: Vec::new(),
170                charged_new_account_state_gas,
171            })))
172        };
173
174        // Check depth
175        if depth > CALL_STACK_LIMIT as usize {
176            return return_result(InstructionResult::CallTooDeep);
177        }
178
179        // Create subroutine checkpoint
180        let checkpoint = ctx.journal_mut().checkpoint();
181
182        // Touch address. For "EIP-158 State Clear", this will erase empty accounts.
183        if let CallValue::Transfer(value) = inputs.value {
184            // Transfer value from caller to called account
185            // Target will get touched even if balance transferred is zero.
186            if let Some(i) =
187                ctx.journal_mut()
188                    .transfer_loaded(inputs.caller, inputs.target_address, value)
189            {
190                ctx.journal_mut().checkpoint_revert(checkpoint);
191                return return_result(i.into());
192            }
193        }
194
195        let interpreter_input = InputsImpl {
196            target_address: inputs.target_address,
197            caller_address: inputs.caller,
198            bytecode_address: Some(inputs.bytecode_address),
199            input: inputs.input.clone(),
200            call_value: inputs.value.get(),
201        };
202        let is_static = inputs.is_static;
203        let gas_limit = inputs.gas_limit;
204
205        if let Some(result) = precompiles.run(ctx, &inputs).map_err(ERROR::from_string)? {
206            let mut logs = Vec::new();
207            if result.result.is_ok() {
208                // Preserve the reservoir on the result gas so it can be reimbursed.
209                // Precompiles don't use reservoir gas, but the first frame carries it.
210                ctx.journal_mut().checkpoint_commit();
211            } else {
212                // clone logs that precompile created, only possible with custom precompiles.
213                // checkpoint.log_i will be always correct.
214                logs = ctx.journal_mut().logs()[checkpoint.log_i..].to_vec();
215                ctx.journal_mut().checkpoint_revert(checkpoint);
216            }
217            return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
218                result,
219                memory_offset: inputs.return_memory_offset.clone(),
220                was_precompile_called: true,
221                precompile_call_logs: logs,
222                charged_new_account_state_gas,
223            })));
224        }
225
226        // Get bytecode and hash - either from known_bytecode or load from account
227        let (bytecode_hash, bytecode) = inputs.known_bytecode.clone();
228
229        // Returns success if bytecode is empty.
230        if bytecode.is_empty() {
231            ctx.journal_mut().checkpoint_commit();
232            return return_result(InstructionResult::Stop);
233        }
234
235        // Create interpreter and executes call and push new CallStackFrame.
236        this.get(EthFrame::invalid).clear(
237            FrameData::Call(CallFrame {
238                return_memory_range: inputs.return_memory_offset.clone(),
239            }),
240            FrameInput::Call(inputs),
241            depth,
242            memory,
243            ExtBytecode::new_with_hash(bytecode, bytecode_hash),
244            interpreter_input,
245            is_static,
246            ctx.cfg().spec().into(),
247            gas_limit,
248            reservoir_remaining_gas,
249            checkpoint,
250        );
251        Ok(ItemOrResult::Item(this.consume()))
252    }
253
254    /// Make create frame.
255    #[inline]
256    pub fn make_create_frame<
257        CTX: ContextTr,
258        ERROR: From<ContextTrDbError<CTX>> + FromStringError,
259    >(
260        mut this: OutFrame<'_, Self>,
261        context: &mut CTX,
262        depth: usize,
263        memory: SharedMemory,
264        inputs: Box<CreateInputs>,
265    ) -> Result<ItemOrResult<FrameToken, FrameResult>, ERROR> {
266        let reservoir_remaining_gas = inputs.reservoir();
267        let spec = context.cfg().spec().into();
268        // EIP-8037 refund for the CREATE opcode's upfront `create_state_gas` is
269        // applied uniformly in `return_result` when the create fails (revert,
270        // halt, or early-fail with `address == None`), so early-fail results
271        // only carry the reservoir they inherited from the parent.
272        let return_error = |e| {
273            Ok(ItemOrResult::Result(FrameResult::Create(CreateOutcome {
274                result: InterpreterResult {
275                    result: e,
276                    gas: Gas::new_with_regular_gas_and_reservoir(
277                        inputs.gas_limit(),
278                        reservoir_remaining_gas,
279                    ),
280                    output: Bytes::new(),
281                },
282                address: None,
283            })))
284        };
285
286        // Check depth
287        if depth > CALL_STACK_LIMIT as usize {
288            return return_error(InstructionResult::CallTooDeep);
289        }
290
291        // Fetch balance of caller.
292        let journal = context.journal_mut();
293        let mut caller_info = journal.load_account_mut(inputs.caller())?;
294
295        // Check if caller has enough balance to send to the created contract.
296        // decrement of balance is done in the create_account_checkpoint.
297        if *caller_info.balance() < inputs.value() {
298            return return_error(InstructionResult::OutOfFunds);
299        }
300
301        // Increase nonce of caller and check if it overflows
302        let old_nonce = caller_info.nonce();
303        if !caller_info.bump_nonce() {
304            return return_error(InstructionResult::Return);
305        };
306
307        // Create address — uses OnceCell cache so that if an inspector already called
308        // `created_address`, the expensive keccak256 is not recomputed.
309        let created_address = inputs.created_address(old_nonce);
310        let init_code_hash = matches!(inputs.scheme(), CreateScheme::Create2 { .. })
311            .then(|| inputs.init_code_hash());
312
313        drop(caller_info); // Drop caller info to avoid borrow checker issues.
314
315        // warm load account.
316        journal.load_account(created_address)?;
317
318        // Create account, transfer funds and make the journal checkpoint.
319        let checkpoint = match context.journal_mut().create_account_checkpoint(
320            inputs.caller(),
321            created_address,
322            inputs.value(),
323            spec,
324        ) {
325            Ok(checkpoint) => checkpoint,
326            Err(e) => return return_error(e.into()),
327        };
328
329        let bytecode = ExtBytecode::new_with_optional_hash(
330            Bytecode::new_legacy(inputs.init_code().clone()),
331            init_code_hash,
332        );
333
334        let interpreter_input = InputsImpl {
335            target_address: created_address,
336            caller_address: inputs.caller(),
337            bytecode_address: None,
338            input: CallInput::Bytes(Bytes::new()),
339            call_value: inputs.value(),
340        };
341        let gas_limit = inputs.gas_limit();
342
343        this.get(EthFrame::invalid).clear(
344            FrameData::Create(CreateFrame { created_address }),
345            FrameInput::Create(inputs),
346            depth,
347            memory,
348            bytecode,
349            interpreter_input,
350            false,
351            spec,
352            gas_limit,
353            reservoir_remaining_gas,
354            checkpoint,
355        );
356
357        Ok(ItemOrResult::Item(this.consume()))
358    }
359
360    /// Initializes a frame with the given context and precompiles.
361    pub fn init_with_context<
362        CTX: ContextTr,
363        PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
364    >(
365        this: OutFrame<'_, Self>,
366        ctx: &mut CTX,
367        precompiles: &mut PRECOMPILES,
368        frame_init: FrameInit,
369    ) -> Result<
370        ItemOrResult<FrameToken, FrameResult>,
371        ContextError<<<CTX as ContextTr>::Db as Database>::Error>,
372    > {
373        // TODO cleanup inner make functions
374        let FrameInit {
375            depth,
376            memory,
377            frame_input,
378        } = frame_init;
379
380        match frame_input {
381            FrameInput::Call(inputs) => {
382                Self::make_call_frame(this, ctx, precompiles, depth, memory, inputs)
383            }
384            FrameInput::Create(inputs) => Self::make_create_frame(this, ctx, depth, memory, inputs),
385            FrameInput::Empty => unreachable!(),
386        }
387    }
388}
389
390impl EthFrame<EthInterpreter> {
391    /// Processes the next interpreter action, either creating a new frame or returning a result.
392    pub fn process_next_action<
393        CTX: ContextTr,
394        ERROR: From<ContextTrDbError<CTX>> + FromStringError,
395    >(
396        &mut self,
397        context: &mut CTX,
398        next_action: InterpreterAction,
399    ) -> Result<FrameInitOrResult<Self>, ERROR> {
400        // Run interpreter
401
402        let mut interpreter_result = match next_action {
403            InterpreterAction::NewFrame(frame_input) => {
404                let depth = self.depth + 1;
405                return Ok(ItemOrResult::Item(FrameInit {
406                    frame_input,
407                    depth,
408                    memory: self.interpreter.memory.new_child_context(),
409                }));
410            }
411            InterpreterAction::Return(result) => result,
412        };
413
414        // Handle return from frame
415        let result = match &self.data {
416            FrameData::Call(frame) => {
417                // return_call
418                // Revert changes or not.
419                if interpreter_result.result.is_ok() {
420                    context.journal_mut().checkpoint_commit();
421                } else {
422                    context.journal_mut().checkpoint_revert(self.checkpoint);
423                }
424                // Propagate EIP-8037 new-account state-gas flag from the frame
425                // input so the parent can refund the upfront charge if the call
426                // ends in revert/halt.
427                let charged_new_account_state_gas = match &self.input {
428                    FrameInput::Call(inputs) => inputs.charged_new_account_state_gas,
429                    _ => false,
430                };
431                let mut outcome =
432                    CallOutcome::new(interpreter_result, frame.return_memory_range.clone());
433                outcome.charged_new_account_state_gas = charged_new_account_state_gas;
434                ItemOrResult::Result(FrameResult::Call(outcome))
435            }
436            FrameData::Create(frame) => {
437                return_create(
438                    context,
439                    self.checkpoint,
440                    &mut interpreter_result,
441                    frame.created_address,
442                );
443
444                ItemOrResult::Result(FrameResult::Create(CreateOutcome::new(
445                    interpreter_result,
446                    Some(frame.created_address),
447                )))
448            }
449        };
450
451        Ok(result)
452    }
453
454    /// Processes a frame result and updates the interpreter state accordingly.
455    pub fn return_result<CTX: ContextTr, ERROR: From<ContextTrDbError<CTX>> + FromStringError>(
456        &mut self,
457        ctx: &mut CTX,
458        result: FrameResult,
459    ) -> Result<(), ERROR> {
460        self.interpreter.memory.free_child_context();
461        take_error::<ERROR, _>(ctx.error())?;
462
463        // Insert result to the top frame.
464        match result {
465            FrameResult::Call(outcome) => {
466                let out_gas = outcome.gas();
467                let ins_result = *outcome.instruction_result();
468                let returned_len = outcome.result.output.len();
469
470                let interpreter = &mut self.interpreter;
471                let mem_length = outcome.memory_length();
472                let mem_start = outcome.memory_start();
473                interpreter.return_data.set_buffer(outcome.result.output);
474
475                let target_len = min(mem_length, returned_len);
476
477                if ins_result == InstructionResult::FatalExternalError {
478                    panic!("Fatal external error in insert_call_outcome");
479                }
480
481                let item = if ins_result.is_ok() {
482                    U256::from(1)
483                } else {
484                    U256::ZERO
485                };
486                // Safe to push without stack limit check
487                let _ = interpreter.stack.push(item);
488
489                // Return unspend gas.
490                if ins_result.is_ok_or_revert() {
491                    interpreter.gas.erase_cost(out_gas.remaining());
492                    interpreter
493                        .memory
494                        .set(mem_start, &interpreter.return_data.buffer()[..target_len]);
495                }
496
497                // handle reservoir remaining gas
498                handle_reservoir_remaining_gas(ins_result, &mut interpreter.gas, &out_gas);
499
500                if ins_result.is_ok() {
501                    interpreter.gas.record_refund(out_gas.refunded());
502                }
503            }
504            FrameResult::Create(outcome) => {
505                let instruction_result = *outcome.instruction_result();
506                let interpreter = &mut self.interpreter;
507
508                if instruction_result == InstructionResult::Revert {
509                    // Save data to return data buffer if the create reverted
510                    interpreter
511                        .return_data
512                        .set_buffer(outcome.output().to_owned());
513                } else {
514                    // Otherwise clear it. Note that RETURN opcode should abort.
515                    interpreter.return_data.clear();
516                };
517
518                assert_ne!(
519                    instruction_result,
520                    InstructionResult::FatalExternalError,
521                    "Fatal external error in insert_eofcreate_outcome"
522                );
523
524                let this_gas = &mut interpreter.gas;
525                // Refund unused gas for success and revert cases.
526                if instruction_result.is_ok_or_revert() {
527                    this_gas.erase_cost(outcome.gas().remaining());
528                }
529
530                // handle reservoir remaining gas
531                handle_reservoir_remaining_gas(instruction_result, this_gas, outcome.gas());
532
533                // EIP-8037: The CREATE opcode charged `create_state_gas` upfront on
534                // this frame's tracker. When the child fails to deploy a contract
535                // (revert, halt, or early-fail paths that return `address == None`
536                // such as nonce overflow, depth, OutOfFunds), refund the upfront
537                // charge to the reservoir and undo it on `state_gas_spent`.
538                // The nonce-overflow path reports `InstructionResult::Return` (ok)
539                // with `address == None`, so gate on address rather than the result.
540                //
541                // Use `refill_reservoir` so that this refund is counted in
542                // `refill_amount` and gets unwound if the parent itself
543                // reverts/halts (matching 0→x→0 storage restoration).
544                let create_failed = outcome.address.is_none() || !instruction_result.is_ok();
545
546                if create_failed && ctx.cfg().is_amsterdam_eip8037_enabled() {
547                    let state_gas_charged =
548                        ctx.cfg().gas_params().create_state_gas(ctx.local().cpsb());
549                    this_gas.refill_reservoir(state_gas_charged);
550                }
551
552                let stack_item = if instruction_result.is_ok() {
553                    this_gas.record_refund(outcome.gas().refunded());
554                    outcome.address.unwrap_or_default().into_word().into()
555                } else {
556                    U256::ZERO
557                };
558
559                // Safe to push without stack limit check
560                let _ = interpreter.stack.push(stack_item);
561            }
562        }
563
564        Ok(())
565    }
566}
567
568/// Handles the remaining gas of the parent frame.
569#[inline]
570pub const fn handle_reservoir_remaining_gas(
571    instruction_result: InstructionResult,
572    parent_gas: &mut Gas,
573    child_gas: &Gas,
574) {
575    if instruction_result.is_ok() {
576        // On success: parent takes the child's final reservoir.
577        parent_gas.set_reservoir(child_gas.reservoir());
578        // Accumulate child's state gas into parent's total.
579        // Parent may have already charged state gas (e.g., new_account + create) before
580        // creating the child frame. Child starts with state_gas_spent=0, so we must add
581        // rather than overwrite to preserve the parent's prior charges.
582        //
583        // `child.state_gas_spent()` can be negative (EIP-8037 issue #2) when the
584        // child did more 0→x→0 restorations than 0→x creations; the negative
585        // contribution is the parent's matching charge flowing back out.
586        parent_gas.set_state_gas_spent(
587            parent_gas
588                .state_gas_spent()
589                .saturating_add(child_gas.state_gas_spent()),
590        );
591        // Propagate child's 0→x→0 refill total so that if the parent
592        // later reverts/halts, the refill contribution can be unwound.
593        parent_gas.set_refill_amount(
594            parent_gas
595                .refill_amount()
596                .saturating_add(child_gas.refill_amount()),
597        );
598    } else {
599        // On revert/halt: the child's state changes are rolled back, so any
600        // 0→x→0 refills the child (or its descendants) credited to the
601        // reservoir must unwind too — the underlying clears no longer exist.
602        //
603        // Invariant when no reservoir→remaining spill happened in the child:
604        //     pre_call_reservoir = child.reservoir + child.state_gas_spent
605        // because every reservoir-funded `record_state_cost(c)` increments
606        // state_gas_spent by `c` while decrementing reservoir by `c`, and every
607        // `refill_reservoir(r)` does the opposite. Adding the (possibly negative)
608        // state_gas_spent back to the final reservoir recovers the pre-call value
609        // — discarding the negative branch (the old `.max(0)`) would leak
610        // grandchild refill credits up through a reverting parent.
611        parent_gas.set_reservoir(
612            child_gas
613                .reservoir()
614                .saturating_add_signed(child_gas.state_gas_spent()),
615        );
616    }
617}
618
619/// Handles the result of a CREATE operation, including validation and state updates.
620///
621/// The EIP-8037 upfront CREATE state gas is charged on the parent's tracker by
622/// the CREATE/CREATE2 opcode. On child failure (revert/halt/early-fail) it is
623/// refunded to the parent in `return_result`. The child frame is NOT allowed to
624/// borrow the upfront charge to pay for code deposit: it must cover code deposit
625/// state gas from its own reservoir and remaining gas.
626pub fn return_create<CTX: ContextTr>(
627    context: &mut CTX,
628    checkpoint: JournalCheckpoint,
629    interpreter_result: &mut InterpreterResult,
630    address: Address,
631) {
632    let (_, _, cfg, journal, _, local) = context.all_mut();
633
634    let max_code_size = cfg.max_code_size();
635    let is_eip3541_disabled = cfg.is_eip3541_disabled();
636    let spec_id = cfg.spec().into();
637    let is_amsterdam_eip8037 = cfg.is_amsterdam_eip8037_enabled();
638    let cpsb = local.cpsb();
639    let gas_params = cfg.gas_params();
640
641    // If return is not ok revert and return.
642    if !interpreter_result.result.is_ok() {
643        journal.checkpoint_revert(checkpoint);
644        return;
645    }
646
647    // EIP-170: Contract code size limit to 0x6000 (~25kb)
648    // EIP-7954 increased this limit to 0x8000 (~32kb).
649    // This must be checked BEFORE charging state gas for code deposit,
650    // so that oversized code does not incur storage gas costs.
651    if spec_id.is_enabled_in(SPURIOUS_DRAGON) && interpreter_result.output.len() > max_code_size {
652        journal.checkpoint_revert(checkpoint);
653        interpreter_result.result = InstructionResult::CreateContractSizeLimit;
654        return;
655    }
656
657    // Host error if present on execution
658    // If ok, check contract creation limit and calculate gas deduction on output len.
659    //
660    // EIP-3541: Reject new contract code starting with the 0xEF byte
661    if !is_eip3541_disabled
662        && spec_id.is_enabled_in(LONDON)
663        && interpreter_result.output.first() == Some(&0xEF)
664    {
665        journal.checkpoint_revert(checkpoint);
666        interpreter_result.result = InstructionResult::CreateContractStartingWithEF;
667        return;
668    }
669
670    // regular gas for code deposit. It is zero in EIP-8037.
671    let gas_for_code = gas_params.code_deposit_cost(interpreter_result.output.len());
672    if !interpreter_result.gas.record_regular_cost(gas_for_code) {
673        // Record code deposit gas cost and check if we are out of gas.
674        // EIP-2 point 3: If contract creation does not have enough gas to pay for the
675        // final gas fee for adding the contract code to the state, the contract
676        // creation fails (i.e. goes out-of-gas) rather than leaving an empty contract.
677        if spec_id.is_enabled_in(HOMESTEAD) {
678            journal.checkpoint_revert(checkpoint);
679            interpreter_result.result = InstructionResult::OutOfGas;
680            return;
681        } else {
682            interpreter_result.output = Bytes::new();
683        }
684    }
685
686    // EIP-8037: Hash cost for deployed bytecode (keccak256)
687    // HASH_COST(L) = 6 × ceil(L / 32)
688    // Both CREATE and CREATE2 must pay this cost: it covers hashing the deployed code
689    // to compute the code_hash stored in the account. CREATE2's existing keccak256 charge
690    // (in create2_cost) is for hashing the init code during address derivation, which is
691    // a different hash.
692    if is_amsterdam_eip8037 {
693        let hash_cost = gas_params.keccak256_cost(interpreter_result.output.len());
694        if !interpreter_result.gas.record_regular_cost(hash_cost) {
695            journal.checkpoint_revert(checkpoint);
696            interpreter_result.result = InstructionResult::OutOfGas;
697            return;
698        }
699        // State gas for code deposit (EIP-8037).
700        // Charged after size check: only code that passes validation incurs state gas cost.
701        //
702        // Note: This should be last operation before checkpoint commit as spending state before this messes
703        // with refilling of state gas.
704        let state_gas_for_code =
705            gas_params.code_deposit_state_gas(interpreter_result.output.len(), cpsb);
706        if state_gas_for_code > 0 && !interpreter_result.gas.record_state_cost(state_gas_for_code) {
707            journal.checkpoint_revert(checkpoint);
708            interpreter_result.result = InstructionResult::OutOfGas;
709            return;
710        }
711    }
712
713    // If we have enough gas we can commit changes.
714    journal.checkpoint_commit();
715
716    // Do analysis of bytecode straight away.
717    let bytecode = Bytecode::new_legacy(interpreter_result.output.clone());
718
719    // Set code
720    journal.set_code(address, bytecode);
721
722    interpreter_result.result = InstructionResult::Return;
723}