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;
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, GasTracker, 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
161        let return_result = |instruction_result: InstructionResult| {
162            Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
163                result: InterpreterResult {
164                    result: instruction_result,
165                    gas,
166                    output: Bytes::new(),
167                },
168                memory_offset: inputs.return_memory_offset.clone(),
169                was_precompile_called: false,
170                precompile_call_logs: Vec::new(),
171                charged_new_account_state_gas,
172            })))
173        };
174
175        // Check depth
176        if depth > CALL_STACK_LIMIT as usize {
177            return return_result(InstructionResult::CallTooDeep);
178        }
179
180        // Create subroutine checkpoint
181        let checkpoint = ctx.journal_mut().checkpoint();
182
183        // Touch address. For "EIP-158 State Clear", this will erase empty accounts.
184        if let CallValue::Transfer(value) = inputs.value {
185            // Transfer value from caller to called account
186            // Target will get touched even if balance transferred is zero.
187            if let Some(i) =
188                ctx.journal_mut()
189                    .transfer_loaded(inputs.caller, inputs.target_address, value)
190            {
191                ctx.journal_mut().checkpoint_revert(checkpoint);
192                return return_result(i.into());
193            }
194        }
195
196        let interpreter_input = InputsImpl {
197            target_address: inputs.target_address,
198            caller_address: inputs.caller,
199            bytecode_address: Some(inputs.bytecode_address),
200            input: inputs.input.clone(),
201            call_value: inputs.value.get(),
202            depth,
203        };
204        let is_static = inputs.is_static;
205        let gas_limit = inputs.gas_limit;
206
207        if let Some(result) = precompiles.run(ctx, &inputs).map_err(ERROR::from_string)? {
208            let mut logs = Vec::new();
209            if result.result.is_ok() {
210                // Preserve the reservoir on the result gas so it can be reimbursed.
211                // Precompiles don't use reservoir gas, but the first frame carries it.
212                ctx.journal_mut().checkpoint_commit();
213            } else {
214                // clone logs that precompile created, only possible with custom precompiles.
215                // checkpoint.log_i will be always correct.
216                logs = ctx.journal_mut().logs()[checkpoint.log_i..].to_vec();
217                ctx.journal_mut().checkpoint_revert(checkpoint);
218            }
219            return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
220                result,
221                memory_offset: inputs.return_memory_offset.clone(),
222                was_precompile_called: true,
223                precompile_call_logs: logs,
224                charged_new_account_state_gas,
225            })));
226        }
227
228        // Get bytecode and hash - either from known_bytecode or load from account
229        let (bytecode_hash, bytecode) = inputs.known_bytecode.clone();
230
231        // Returns success if bytecode is empty.
232        if bytecode.is_empty() {
233            ctx.journal_mut().checkpoint_commit();
234            return return_result(InstructionResult::Stop);
235        }
236
237        // Create interpreter and executes call and push new CallStackFrame.
238        this.get(EthFrame::invalid).clear(
239            FrameData::Call(CallFrame {
240                return_memory_range: inputs.return_memory_offset.clone(),
241            }),
242            FrameInput::Call(inputs),
243            depth,
244            memory,
245            ExtBytecode::new_with_hash(bytecode, bytecode_hash),
246            interpreter_input,
247            is_static,
248            ctx.cfg().spec().into(),
249            gas_limit,
250            reservoir_remaining_gas,
251            checkpoint,
252        );
253
254        Ok(ItemOrResult::Item(this.consume()))
255    }
256
257    /// Make create frame.
258    #[inline]
259    pub fn make_create_frame<
260        CTX: ContextTr,
261        ERROR: From<ContextTrDbError<CTX>> + FromStringError,
262    >(
263        mut this: OutFrame<'_, Self>,
264        context: &mut CTX,
265        depth: usize,
266        memory: SharedMemory,
267        inputs: Box<CreateInputs>,
268    ) -> Result<ItemOrResult<FrameToken, FrameResult>, ERROR> {
269        let reservoir_remaining_gas = inputs.reservoir();
270        let spec = context.cfg().spec().into();
271        // EIP-8037 refund for the CREATE opcode's upfront `create_state_gas` is
272        // applied uniformly in `return_result` when the create fails (revert,
273        // halt, or early-fail with `address == None`), so early-fail results
274        // only carry the reservoir they inherited from the parent.
275        let charged_create_state_gas = inputs.charged_create_state_gas();
276        let return_error = |e| {
277            Ok(ItemOrResult::Result(FrameResult::Create(CreateOutcome {
278                result: InterpreterResult {
279                    result: e,
280                    gas: Gas::new_with_regular_gas_and_reservoir(
281                        inputs.gas_limit(),
282                        reservoir_remaining_gas,
283                    ),
284                    output: Bytes::new(),
285                },
286                address: None,
287                charged_create_state_gas,
288            })))
289        };
290
291        // Check depth
292        if depth > CALL_STACK_LIMIT as usize {
293            return return_error(InstructionResult::CallTooDeep);
294        }
295
296        // Fetch balance of caller.
297        let journal = context.journal_mut();
298        let mut caller_info = journal.load_account_mut(inputs.caller())?;
299
300        // Check if caller has enough balance to send to the created contract.
301        // decrement of balance is done in the create_account_checkpoint.
302        if *caller_info.balance() < inputs.value() {
303            return return_error(InstructionResult::OutOfFunds);
304        }
305
306        // Increase nonce of caller and check if it overflows
307        let old_nonce = caller_info.nonce();
308        if !caller_info.bump_nonce() {
309            return return_error(InstructionResult::Return);
310        };
311
312        // Create address — uses OnceCell cache so that if an inspector already called
313        // `created_address`, the expensive keccak256 is not recomputed.
314        let created_address = inputs.created_address(old_nonce);
315        let init_code_hash = matches!(inputs.scheme(), CreateScheme::Create2 { .. })
316            .then(|| inputs.init_code_hash());
317
318        drop(caller_info); // Drop caller info to avoid borrow checker issues.
319
320        // warm load account.
321        journal.load_account(created_address)?;
322
323        // Create account, transfer funds and make the journal checkpoint.
324        let checkpoint = match context.journal_mut().create_account_checkpoint(
325            inputs.caller(),
326            created_address,
327            inputs.value(),
328            spec,
329        ) {
330            Ok(checkpoint) => checkpoint,
331            Err(e) => return return_error(e.into()),
332        };
333
334        let bytecode = ExtBytecode::new_with_optional_hash(
335            Bytecode::new_legacy(inputs.init_code().clone()),
336            init_code_hash,
337        );
338
339        let interpreter_input = InputsImpl {
340            target_address: created_address,
341            caller_address: inputs.caller(),
342            bytecode_address: None,
343            input: CallInput::Bytes(Bytes::new()),
344            call_value: inputs.value(),
345            depth,
346        };
347        let gas_limit = inputs.gas_limit();
348
349        this.get(EthFrame::invalid).clear(
350            FrameData::Create(CreateFrame { created_address }),
351            FrameInput::Create(inputs),
352            depth,
353            memory,
354            bytecode,
355            interpreter_input,
356            false,
357            spec,
358            gas_limit,
359            reservoir_remaining_gas,
360            checkpoint,
361        );
362
363        Ok(ItemOrResult::Item(this.consume()))
364    }
365
366    /// Initializes a frame with the given context and precompiles.
367    pub fn init_with_context<
368        CTX: ContextTr,
369        PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
370    >(
371        this: OutFrame<'_, Self>,
372        ctx: &mut CTX,
373        precompiles: &mut PRECOMPILES,
374        frame_init: FrameInit,
375    ) -> Result<
376        ItemOrResult<FrameToken, FrameResult>,
377        ContextError<<<CTX as ContextTr>::Db as Database>::Error>,
378    > {
379        // TODO cleanup inner make functions
380        let FrameInit {
381            depth,
382            memory,
383            frame_input,
384        } = frame_init;
385
386        match frame_input {
387            FrameInput::Call(inputs) => {
388                Self::make_call_frame(this, ctx, precompiles, depth, memory, inputs)
389            }
390            FrameInput::Create(inputs) => Self::make_create_frame(this, ctx, depth, memory, inputs),
391            FrameInput::Empty => unreachable!(),
392        }
393    }
394}
395
396impl EthFrame<EthInterpreter> {
397    /// Processes the next interpreter action, either creating a new frame or returning a result.
398    pub fn process_next_action<
399        CTX: ContextTr,
400        ERROR: From<ContextTrDbError<CTX>> + FromStringError,
401    >(
402        &mut self,
403        context: &mut CTX,
404        next_action: InterpreterAction,
405    ) -> Result<FrameInitOrResult<Self>, ERROR> {
406        // Run interpreter
407
408        let mut interpreter_result = match next_action {
409            InterpreterAction::NewFrame(frame_input) => {
410                let depth = self.depth + 1;
411                return Ok(ItemOrResult::Item(FrameInit {
412                    frame_input,
413                    depth,
414                    memory: self.interpreter.memory.new_child_context(),
415                }));
416            }
417            InterpreterAction::Return(result) => result,
418        };
419
420        // Handle return from frame
421        let result = match &self.data {
422            FrameData::Call(frame) => {
423                // return_call
424                // Revert changes or not.
425                if interpreter_result.result.is_ok() {
426                    context.journal_mut().checkpoint_commit();
427                } else {
428                    context.journal_mut().checkpoint_revert(self.checkpoint);
429                }
430                // Propagate EIP-8037 new-account state-gas flag from the frame
431                // input so the parent can refund the upfront charge if the call
432                // ends in revert/halt.
433                let charged_new_account_state_gas = match &self.input {
434                    FrameInput::Call(inputs) => inputs.charged_new_account_state_gas,
435                    _ => false,
436                };
437                let mut outcome =
438                    CallOutcome::new(interpreter_result, frame.return_memory_range.clone());
439                outcome.charged_new_account_state_gas = charged_new_account_state_gas;
440                ItemOrResult::Result(FrameResult::Call(outcome))
441            }
442            FrameData::Create(frame) => {
443                return_create(
444                    context,
445                    self.checkpoint,
446                    &mut interpreter_result,
447                    frame.created_address,
448                );
449
450                let mut create_outcome =
451                    CreateOutcome::new(interpreter_result, Some(frame.created_address));
452                create_outcome.charged_create_state_gas = match &self.input {
453                    FrameInput::Create(inputs) => inputs.charged_create_state_gas(),
454                    _ => false,
455                };
456                ItemOrResult::Result(FrameResult::Create(create_outcome))
457            }
458        };
459
460        Ok(result)
461    }
462
463    /// Processes a frame result and updates the interpreter state accordingly.
464    pub fn return_result<CTX: ContextTr, ERROR: From<ContextTrDbError<CTX>> + FromStringError>(
465        &mut self,
466        ctx: &mut CTX,
467        result: FrameResult,
468    ) -> Result<(), ERROR> {
469        self.interpreter.memory.free_child_context();
470        take_error::<ERROR, _>(ctx.error())?;
471
472        // EIP-8037: the CALL/CREATE opcode charged the new-account or
473        // create state gas upfront on this (parent) frame's tracker. When the
474        // child does not create the account leaf it paid for, the charge is
475        // refunded below via `refill_reservoir` (matching 0→x→0 storage
476        // restoration) — the child rollback in `handle_reservoir_remaining_gas`
477        // cannot do it, since the charge lives on the parent, not the child.
478        let refund_state_gas = result.refundable_state_gas(ctx.cfg().gas_params());
479
480        // Insert result to the top frame.
481        match result {
482            FrameResult::Call(outcome) => {
483                let mut out_gas = outcome.gas();
484                let ins_result = *outcome.instruction_result();
485                let returned_len = outcome.result.output.len();
486
487                let interpreter = &mut self.interpreter;
488                let mem_length = outcome.memory_length();
489                let mem_start = outcome.memory_start();
490                interpreter.return_data.set_buffer(outcome.result.output);
491
492                let target_len = min(mem_length, returned_len);
493
494                if ins_result == InstructionResult::FatalExternalError {
495                    panic!("Fatal external error in insert_call_outcome");
496                }
497
498                let item = if ins_result.is_ok() {
499                    U256::from(1)
500                } else {
501                    U256::ZERO
502                };
503                // Safe to push without stack limit check
504                let _ = interpreter.stack.push(item);
505
506                // Copy returned data into the parent's memory on success or revert.
507                if ins_result.is_ok_or_revert() {
508                    interpreter
509                        .memory
510                        .set(mem_start, &interpreter.return_data.buffer()[..target_len]);
511                }
512
513                // Settle the child's gas and merge it into the parent (returns
514                // unused regular gas, adopts the reservoir, and propagates state
515                // gas / refunds on success).
516                handle_reservoir_remaining_gas(
517                    ins_result,
518                    interpreter.gas.tracker_mut(),
519                    out_gas.tracker_mut(),
520                );
521            }
522            FrameResult::Create(outcome) => {
523                let instruction_result = *outcome.instruction_result();
524                let interpreter = &mut self.interpreter;
525
526                if instruction_result == InstructionResult::Revert {
527                    // Save data to return data buffer if the create reverted
528                    interpreter
529                        .return_data
530                        .set_buffer(outcome.output().to_owned());
531                } else {
532                    // Otherwise clear it. Note that RETURN opcode should abort.
533                    interpreter.return_data.clear();
534                };
535
536                assert_ne!(
537                    instruction_result,
538                    InstructionResult::FatalExternalError,
539                    "Fatal external error in insert_eofcreate_outcome"
540                );
541
542                let mut create_gas = *outcome.gas();
543
544                // Settle the child's gas and merge it into the parent (returns
545                // unused regular gas, adopts the reservoir, and propagates state
546                // gas / refunds on success).
547                handle_reservoir_remaining_gas(
548                    instruction_result,
549                    interpreter.gas.tracker_mut(),
550                    create_gas.tracker_mut(),
551                );
552
553                let stack_item = if instruction_result.is_ok() {
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        // Refund the upfront state charge after the child's gas is settled
565        // (the settle overwrites the reservoir with the child's).
566        if let Some(charge) = refund_state_gas {
567            self.interpreter.gas.refill_reservoir(charge);
568        }
569
570        Ok(())
571    }
572}
573
574/// Settles a returning child frame's gas and merges it into the parent
575/// (EIP-8037 reservoir model).
576///
577/// First the child *settles its own gas*: a failing frame (revert or halt) rolls
578/// its state-gas charges back in last-in-first-out order
579/// ([`GasTracker::rollback_state_gas`]) — crediting the spilled portion back to its
580/// `remaining` and restoring the reservoir to the value it inherited — and drops
581/// its execution refund counter; an exceptional halt additionally consumes the
582/// child's regular gas.
583///
584/// Then the parent *merges* the settled child:
585/// - unused regular gas (`remaining`, including any spill returned on revert)
586///   flows back to the parent on success or revert; a halt consumes it.
587/// - the reservoir, a shared state-gas pool the child inherited at call time, is
588///   always adopted from the child (restored to the inherited value on
589///   revert/halt). Any returned reservoir first unwinds the parent's outstanding
590///   spilled state gas in LIFO order.
591/// - net state gas, its spilled portion, and the refund counter persist only on
592///   success; on revert/halt the child's state changes roll back and contribute
593///   nothing.
594#[inline]
595pub const fn handle_reservoir_remaining_gas(
596    instruction_result: InstructionResult,
597    parent_gas: &mut GasTracker,
598    child_gas: &mut GasTracker,
599) {
600    // Settle the child's own gas for its stop reason.
601    if !instruction_result.is_ok() {
602        child_gas.rollback_state_gas();
603        child_gas.set_refunded(0);
604    }
605    if instruction_result.is_halt() {
606        // Exceptional halt consumes the child's regular gas (including the spill
607        // just credited back by `rollback_state_gas`); the reservoir is left
608        // restored to the inherited value for the parent.
609        child_gas.spend_all();
610    }
611
612    // Merge the settled child into the parent.
613    if instruction_result.is_ok_or_revert() {
614        parent_gas.erase_cost(child_gas.remaining());
615    }
616    if instruction_result.is_ok() {
617        // Parent may have already charged state gas (e.g. new_account + create)
618        // before creating the child frame, so add rather than overwrite. The
619        // child's `state_gas_spent` can be negative (EIP-8037 issue #2) when it
620        // did more 0→x→0 restorations than 0→x creations; the negative
621        // contribution is the parent's matching charge flowing back out.
622        parent_gas.set_state_gas_spent(
623            parent_gas
624                .state_gas_spent()
625                .saturating_add(child_gas.state_gas_spent()),
626        );
627        parent_gas.add_state_gas_spilled(child_gas.state_gas_spilled());
628        parent_gas.record_refund(child_gas.refunded());
629    }
630    parent_gas.absorb_returned_reservoir(child_gas.reservoir());
631}
632
633/// Handles the result of a CREATE operation, including validation and state updates.
634///
635/// The EIP-8037 upfront CREATE state gas is charged on the parent's tracker by
636/// the CREATE/CREATE2 opcode. On child failure (revert/halt/early-fail) it is
637/// refunded to the parent in `return_result`. The child frame is NOT allowed to
638/// borrow the upfront charge to pay for code deposit: it must cover code deposit
639/// state gas from its own reservoir and remaining gas.
640pub fn return_create<CTX: ContextTr>(
641    context: &mut CTX,
642    checkpoint: JournalCheckpoint,
643    interpreter_result: &mut InterpreterResult,
644    address: Address,
645) {
646    let (_, _, cfg, journal, _, _) = context.all_mut();
647
648    let max_code_size = cfg.max_code_size();
649    let is_eip3541_disabled = cfg.is_eip3541_disabled();
650    let spec_id = cfg.spec().into();
651    let is_amsterdam_eip8037 = cfg.is_amsterdam_eip8037_enabled();
652    let gas_params = cfg.gas_params();
653
654    // If return is not ok revert and return.
655    if !interpreter_result.result.is_ok() {
656        journal.checkpoint_revert(checkpoint);
657        return;
658    }
659
660    // EIP-170: Contract code size limit to 0x6000 (~25kb)
661    // EIP-7954 increased this limit to 0x10000 (64kb).
662    // This must be checked BEFORE charging state gas for code deposit,
663    // so that oversized code does not incur storage gas costs.
664    if spec_id.is_enabled_in(SPURIOUS_DRAGON) && interpreter_result.output.len() > max_code_size {
665        journal.checkpoint_revert(checkpoint);
666        interpreter_result.result = InstructionResult::CreateContractSizeLimit;
667        return;
668    }
669
670    // Host error if present on execution
671    // If ok, check contract creation limit and calculate gas deduction on output len.
672    //
673    // EIP-3541: Reject new contract code starting with the 0xEF byte
674    if !is_eip3541_disabled
675        && spec_id.is_enabled_in(LONDON)
676        && interpreter_result.output.first() == Some(&0xEF)
677    {
678        journal.checkpoint_revert(checkpoint);
679        interpreter_result.result = InstructionResult::CreateContractStartingWithEF;
680        return;
681    }
682
683    // regular gas for code deposit. It is zero in EIP-8037.
684    let gas_for_code = gas_params.code_deposit_cost(interpreter_result.output.len());
685    if !interpreter_result.gas.record_regular_cost(gas_for_code) {
686        // Record code deposit gas cost and check if we are out of gas.
687        // EIP-2 point 3: If contract creation does not have enough gas to pay for the
688        // final gas fee for adding the contract code to the state, the contract
689        // creation fails (i.e. goes out-of-gas) rather than leaving an empty contract.
690        if spec_id.is_enabled_in(HOMESTEAD) {
691            journal.checkpoint_revert(checkpoint);
692            interpreter_result.result = InstructionResult::OutOfGas;
693            return;
694        } else {
695            interpreter_result.output = Bytes::new();
696        }
697    }
698
699    // EIP-8037: Hash cost for deployed bytecode (keccak256)
700    // HASH_COST(L) = 6 × ceil(L / 32)
701    // Both CREATE and CREATE2 must pay this cost: it covers hashing the deployed code
702    // to compute the code_hash stored in the account. CREATE2's existing keccak256 charge
703    // (in create2_cost) is for hashing the init code during address derivation, which is
704    // a different hash.
705    if is_amsterdam_eip8037 {
706        let hash_cost = gas_params.keccak256_cost(interpreter_result.output.len());
707        if !interpreter_result.gas.record_regular_cost(hash_cost) {
708            journal.checkpoint_revert(checkpoint);
709            interpreter_result.result = InstructionResult::OutOfGas;
710            return;
711        }
712        // State gas for code deposit (EIP-8037).
713        // Charged after size check: only code that passes validation incurs state gas cost.
714        //
715        // Note: This should be last operation before checkpoint commit as spending state before this messes
716        // with refilling of state gas.
717        let state_gas_for_code = gas_params.code_deposit_state_gas(interpreter_result.output.len());
718        if state_gas_for_code > 0 && !interpreter_result.gas.record_state_cost(state_gas_for_code) {
719            journal.checkpoint_revert(checkpoint);
720            interpreter_result.result = InstructionResult::OutOfGas;
721            return;
722        }
723    }
724
725    // If we have enough gas we can commit changes.
726    journal.checkpoint_commit();
727
728    // Do analysis of bytecode straight away.
729    let bytecode = Bytecode::new_legacy(interpreter_result.output.clone());
730
731    // Set code
732    journal.set_code(address, bytecode);
733
734    interpreter_result.result = InstructionResult::Return;
735}
736
737#[cfg(test)]
738mod tests {
739    use super::*;
740
741    #[test]
742    fn sibling_refill_restores_parent_regular_gas() {
743        const STATE_GAS: u64 = 200;
744        const CHILD_GAS: u64 = 500;
745
746        let mut parent = GasTracker::new(1_000, 1_000, 0);
747
748        // P calls A. A creates a slot after the reservoir is exhausted, so the
749        // charge spills into regular gas and is absorbed by P on success.
750        assert!(parent.record_regular_cost(CHILD_GAS));
751        let mut child_a = GasTracker::new(CHILD_GAS, CHILD_GAS, 0);
752        assert!(child_a.record_state_cost(STATE_GAS));
753        handle_reservoir_remaining_gas(InstructionResult::Stop, &mut parent, &mut child_a);
754        assert_eq!(parent.remaining(), 800);
755        assert_eq!(parent.reservoir(), 0);
756        assert_eq!(parent.state_gas_spent(), STATE_GAS as i64);
757        assert_eq!(parent.state_gas_spilled(), STATE_GAS);
758
759        // P then calls B. B clears A's slot, but has no local spill counter, so
760        // its refill initially lands in its reservoir.
761        assert!(parent.record_regular_cost(CHILD_GAS));
762        let mut child_b = GasTracker::new(CHILD_GAS, CHILD_GAS, parent.reservoir());
763        child_b.refill_reservoir(STATE_GAS);
764        assert_eq!(child_b.reservoir(), STATE_GAS);
765        assert_eq!(child_b.state_gas_spilled(), 0);
766
767        // On success, P absorbs B's refill in global LIFO order: the reservoir
768        // is moved back to regular gas to unwind A's spilled charge.
769        handle_reservoir_remaining_gas(InstructionResult::Stop, &mut parent, &mut child_b);
770        assert_eq!(parent.remaining(), 1_000);
771        assert_eq!(parent.reservoir(), 0);
772        assert_eq!(parent.state_gas_spent(), 0);
773        assert_eq!(parent.state_gas_spilled(), 0);
774    }
775}