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, 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 gas =
158            Gas::new_with_regular_gas_and_reservoir(inputs.gas_limit, reservoir_remaining_gas);
159        let return_result = |instruction_result: InstructionResult| {
160            Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
161                result: InterpreterResult {
162                    result: instruction_result,
163                    gas,
164                    output: Bytes::new(),
165                },
166                memory_offset: inputs.return_memory_offset.clone(),
167                was_precompile_called: false,
168                precompile_call_logs: Vec::new(),
169            })))
170        };
171
172        // Check depth
173        if depth > CALL_STACK_LIMIT as usize {
174            return return_result(InstructionResult::CallTooDeep);
175        }
176
177        // Create subroutine checkpoint
178        let checkpoint = ctx.journal_mut().checkpoint();
179
180        // Touch address. For "EIP-158 State Clear", this will erase empty accounts.
181        if let CallValue::Transfer(value) = inputs.value {
182            // Transfer value from caller to called account
183            // Target will get touched even if balance transferred is zero.
184            if let Some(i) =
185                ctx.journal_mut()
186                    .transfer_loaded(inputs.caller, inputs.target_address, value)
187            {
188                ctx.journal_mut().checkpoint_revert(checkpoint);
189                return return_result(i.into());
190            }
191        }
192
193        let interpreter_input = InputsImpl {
194            target_address: inputs.target_address,
195            caller_address: inputs.caller,
196            bytecode_address: Some(inputs.bytecode_address),
197            input: inputs.input.clone(),
198            call_value: inputs.value.get(),
199        };
200        let is_static = inputs.is_static;
201        let gas_limit = inputs.gas_limit;
202
203        if let Some(result) = precompiles.run(ctx, &inputs).map_err(ERROR::from_string)? {
204            let mut logs = Vec::new();
205            if result.result.is_ok() {
206                // Preserve the reservoir on the result gas so it can be reimbursed.
207                // Precompiles don't use reservoir gas, but the first frame carries it.
208                ctx.journal_mut().checkpoint_commit();
209            } else {
210                // clone logs that precompile created, only possible with custom precompiles.
211                // checkpoint.log_i will be always correct.
212                logs = ctx.journal_mut().logs()[checkpoint.log_i..].to_vec();
213                ctx.journal_mut().checkpoint_revert(checkpoint);
214            }
215            return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
216                result,
217                memory_offset: inputs.return_memory_offset.clone(),
218                was_precompile_called: true,
219                precompile_call_logs: logs,
220            })));
221        }
222
223        // Get bytecode and hash - either from known_bytecode or load from account
224        let (bytecode_hash, bytecode) = inputs.known_bytecode.clone();
225
226        // Returns success if bytecode is empty.
227        if bytecode.is_empty() {
228            ctx.journal_mut().checkpoint_commit();
229            return return_result(InstructionResult::Stop);
230        }
231
232        // Create interpreter and executes call and push new CallStackFrame.
233        this.get(EthFrame::invalid).clear(
234            FrameData::Call(CallFrame {
235                return_memory_range: inputs.return_memory_offset.clone(),
236            }),
237            FrameInput::Call(inputs),
238            depth,
239            memory,
240            ExtBytecode::new_with_hash(bytecode, bytecode_hash),
241            interpreter_input,
242            is_static,
243            ctx.cfg().spec().into(),
244            gas_limit,
245            reservoir_remaining_gas,
246            checkpoint,
247        );
248        Ok(ItemOrResult::Item(this.consume()))
249    }
250
251    /// Make create frame.
252    #[inline]
253    pub fn make_create_frame<
254        CTX: ContextTr,
255        ERROR: From<ContextTrDbError<CTX>> + FromStringError,
256    >(
257        mut this: OutFrame<'_, Self>,
258        context: &mut CTX,
259        depth: usize,
260        memory: SharedMemory,
261        inputs: Box<CreateInputs>,
262    ) -> Result<ItemOrResult<FrameToken, FrameResult>, ERROR> {
263        let reservoir_remaining_gas = inputs.reservoir();
264        let spec = context.cfg().spec().into();
265        let return_error = |e| {
266            Ok(ItemOrResult::Result(FrameResult::Create(CreateOutcome {
267                result: InterpreterResult {
268                    result: e,
269                    gas: Gas::new_with_regular_gas_and_reservoir(
270                        inputs.gas_limit(),
271                        reservoir_remaining_gas,
272                    ),
273                    output: Bytes::new(),
274                },
275                address: None,
276            })))
277        };
278
279        // Check depth
280        if depth > CALL_STACK_LIMIT as usize {
281            return return_error(InstructionResult::CallTooDeep);
282        }
283
284        // Fetch balance of caller.
285        let journal = context.journal_mut();
286        let mut caller_info = journal.load_account_mut(inputs.caller())?;
287
288        // Check if caller has enough balance to send to the created contract.
289        // decrement of balance is done in the create_account_checkpoint.
290        if *caller_info.balance() < inputs.value() {
291            return return_error(InstructionResult::OutOfFunds);
292        }
293
294        // Increase nonce of caller and check if it overflows
295        let old_nonce = caller_info.nonce();
296        if !caller_info.bump_nonce() {
297            return return_error(InstructionResult::Return);
298        };
299
300        // Create address — uses OnceCell cache so that if an inspector already called
301        // `created_address`, the expensive keccak256 is not recomputed.
302        let created_address = inputs.created_address(old_nonce);
303        let init_code_hash = matches!(inputs.scheme(), CreateScheme::Create2 { .. })
304            .then(|| inputs.init_code_hash());
305
306        drop(caller_info); // Drop caller info to avoid borrow checker issues.
307
308        // warm load account.
309        journal.load_account(created_address)?;
310
311        // Create account, transfer funds and make the journal checkpoint.
312        let checkpoint = match context.journal_mut().create_account_checkpoint(
313            inputs.caller(),
314            created_address,
315            inputs.value(),
316            spec,
317        ) {
318            Ok(checkpoint) => checkpoint,
319            Err(e) => return return_error(e.into()),
320        };
321
322        let bytecode = ExtBytecode::new_with_optional_hash(
323            Bytecode::new_legacy(inputs.init_code().clone()),
324            init_code_hash,
325        );
326
327        let interpreter_input = InputsImpl {
328            target_address: created_address,
329            caller_address: inputs.caller(),
330            bytecode_address: None,
331            input: CallInput::Bytes(Bytes::new()),
332            call_value: inputs.value(),
333        };
334        let gas_limit = inputs.gas_limit();
335
336        this.get(EthFrame::invalid).clear(
337            FrameData::Create(CreateFrame { created_address }),
338            FrameInput::Create(inputs),
339            depth,
340            memory,
341            bytecode,
342            interpreter_input,
343            false,
344            spec,
345            gas_limit,
346            reservoir_remaining_gas,
347            checkpoint,
348        );
349
350        Ok(ItemOrResult::Item(this.consume()))
351    }
352
353    /// Initializes a frame with the given context and precompiles.
354    pub fn init_with_context<
355        CTX: ContextTr,
356        PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
357    >(
358        this: OutFrame<'_, Self>,
359        ctx: &mut CTX,
360        precompiles: &mut PRECOMPILES,
361        frame_init: FrameInit,
362    ) -> Result<
363        ItemOrResult<FrameToken, FrameResult>,
364        ContextError<<<CTX as ContextTr>::Db as Database>::Error>,
365    > {
366        // TODO cleanup inner make functions
367        let FrameInit {
368            depth,
369            memory,
370            frame_input,
371        } = frame_init;
372
373        match frame_input {
374            FrameInput::Call(inputs) => {
375                Self::make_call_frame(this, ctx, precompiles, depth, memory, inputs)
376            }
377            FrameInput::Create(inputs) => Self::make_create_frame(this, ctx, depth, memory, inputs),
378            FrameInput::Empty => unreachable!(),
379        }
380    }
381}
382
383impl EthFrame<EthInterpreter> {
384    /// Processes the next interpreter action, either creating a new frame or returning a result.
385    pub fn process_next_action<
386        CTX: ContextTr,
387        ERROR: From<ContextTrDbError<CTX>> + FromStringError,
388    >(
389        &mut self,
390        context: &mut CTX,
391        next_action: InterpreterAction,
392    ) -> Result<FrameInitOrResult<Self>, ERROR> {
393        // Run interpreter
394
395        let mut interpreter_result = match next_action {
396            InterpreterAction::NewFrame(frame_input) => {
397                let depth = self.depth + 1;
398                return Ok(ItemOrResult::Item(FrameInit {
399                    frame_input,
400                    depth,
401                    memory: self.interpreter.memory.new_child_context(),
402                }));
403            }
404            InterpreterAction::Return(result) => result,
405        };
406
407        // Handle return from frame
408        let result = match &self.data {
409            FrameData::Call(frame) => {
410                // return_call
411                // Revert changes or not.
412                if interpreter_result.result.is_ok() {
413                    context.journal_mut().checkpoint_commit();
414                } else {
415                    context.journal_mut().checkpoint_revert(self.checkpoint);
416                }
417                ItemOrResult::Result(FrameResult::Call(CallOutcome::new(
418                    interpreter_result,
419                    frame.return_memory_range.clone(),
420                )))
421            }
422            FrameData::Create(frame) => {
423                let (cfg, journal) = context.cfg_journal_mut();
424                return_create(
425                    journal,
426                    cfg,
427                    self.checkpoint,
428                    &mut interpreter_result,
429                    frame.created_address,
430                );
431
432                ItemOrResult::Result(FrameResult::Create(CreateOutcome::new(
433                    interpreter_result,
434                    Some(frame.created_address),
435                )))
436            }
437        };
438
439        Ok(result)
440    }
441
442    /// Processes a frame result and updates the interpreter state accordingly.
443    pub fn return_result<CTX: ContextTr, ERROR: From<ContextTrDbError<CTX>> + FromStringError>(
444        &mut self,
445        ctx: &mut CTX,
446        result: FrameResult,
447    ) -> Result<(), ERROR> {
448        self.interpreter.memory.free_child_context();
449        take_error::<ERROR, _>(ctx.error())?;
450
451        // Insert result to the top frame.
452        match result {
453            FrameResult::Call(outcome) => {
454                let out_gas = outcome.gas();
455                let ins_result = *outcome.instruction_result();
456                let returned_len = outcome.result.output.len();
457
458                let interpreter = &mut self.interpreter;
459                let mem_length = outcome.memory_length();
460                let mem_start = outcome.memory_start();
461                interpreter.return_data.set_buffer(outcome.result.output);
462
463                let target_len = min(mem_length, returned_len);
464
465                if ins_result == InstructionResult::FatalExternalError {
466                    panic!("Fatal external error in insert_call_outcome");
467                }
468
469                let item = if ins_result.is_ok() {
470                    U256::from(1)
471                } else {
472                    U256::ZERO
473                };
474                // Safe to push without stack limit check
475                let _ = interpreter.stack.push(item);
476
477                // Return unspend gas.
478                if ins_result.is_ok_or_revert() {
479                    interpreter.gas.erase_cost(out_gas.remaining());
480                    interpreter
481                        .memory
482                        .set(mem_start, &interpreter.return_data.buffer()[..target_len]);
483                }
484
485                // handle reservoir remaining gas
486                handle_reservoir_remaining_gas(&mut interpreter.gas, &out_gas, ins_result);
487
488                if ins_result.is_ok() {
489                    interpreter.gas.record_refund(out_gas.refunded());
490                }
491            }
492            FrameResult::Create(outcome) => {
493                let instruction_result = *outcome.instruction_result();
494                let interpreter = &mut self.interpreter;
495
496                if instruction_result == InstructionResult::Revert {
497                    // Save data to return data buffer if the create reverted
498                    interpreter
499                        .return_data
500                        .set_buffer(outcome.output().to_owned());
501                } else {
502                    // Otherwise clear it. Note that RETURN opcode should abort.
503                    interpreter.return_data.clear();
504                };
505
506                assert_ne!(
507                    instruction_result,
508                    InstructionResult::FatalExternalError,
509                    "Fatal external error in insert_eofcreate_outcome"
510                );
511
512                let this_gas = &mut interpreter.gas;
513                // Refund unused gas for success and revert cases.
514                if instruction_result.is_ok_or_revert() {
515                    this_gas.erase_cost(outcome.gas().remaining());
516                }
517
518                // handle reservoir remaining gas
519                handle_reservoir_remaining_gas(this_gas, outcome.gas(), instruction_result);
520
521                let stack_item = if instruction_result.is_ok() {
522                    this_gas.record_refund(outcome.gas().refunded());
523                    outcome.address.unwrap_or_default().into_word().into()
524                } else {
525                    U256::ZERO
526                };
527
528                // Safe to push without stack limit check
529                let _ = interpreter.stack.push(stack_item);
530            }
531        }
532
533        Ok(())
534    }
535}
536
537/// Handles the remaining gas of the parent frame.
538#[inline]
539pub const fn handle_reservoir_remaining_gas(
540    parent_gas: &mut Gas,
541    child_gas: &Gas,
542    result: InstructionResult,
543) {
544    if result.is_ok() {
545        // On success: parent takes the child's final reservoir.
546        parent_gas.set_reservoir(child_gas.reservoir());
547        // Accumulate child's state gas into parent's total.
548        // Parent may have already charged state gas (e.g., new_account + create) before
549        // creating the child frame. Child starts with state_gas_spent=0, so we must add
550        // rather than overwrite to preserve the parent's prior charges.
551        parent_gas.set_state_gas_spent(parent_gas.state_gas_spent() + child_gas.state_gas_spent());
552    } else {
553        // On revert or halt: state changes are undone, so ALL state gas returns
554        // to the parent's reservoir.
555        // - child.state_gas_spent(): state gas the child consumed (state rolled back, so refunded)
556        // - child.reservoir(): state gas the child didn't use (including gas returned from
557        //   deeper failed frames)
558        // This replaces (not adds to) the parent's reservoir because the child started with
559        // the parent's reservoir value (REVM doesn't zero it before the call), so the child's
560        // total already includes the parent's original reservoir.
561        parent_gas.set_reservoir(child_gas.state_gas_spent() + child_gas.reservoir());
562    }
563}
564
565/// Handles the result of a CREATE operation, including validation and state updates.
566pub fn return_create<JOURNAL: JournalTr, CFG: Cfg>(
567    journal: &mut JOURNAL,
568    cfg: CFG,
569    checkpoint: JournalCheckpoint,
570    interpreter_result: &mut InterpreterResult,
571    address: Address,
572) {
573    let max_code_size = cfg.max_code_size();
574    let is_eip3541_disabled = cfg.is_eip3541_disabled();
575    let spec_id = cfg.spec().into();
576
577    // If return is not ok revert and return.
578    if !interpreter_result.result.is_ok() {
579        journal.checkpoint_revert(checkpoint);
580        return;
581    }
582
583    // EIP-170: Contract code size limit to 0x6000 (~25kb)
584    // EIP-7954 increased this limit to 0x8000 (~32kb).
585    // This must be checked BEFORE charging state gas for code deposit,
586    // so that oversized code does not incur storage gas costs.
587    if spec_id.is_enabled_in(SPURIOUS_DRAGON) && interpreter_result.output.len() > max_code_size {
588        journal.checkpoint_revert(checkpoint);
589        interpreter_result.result = InstructionResult::CreateContractSizeLimit;
590        return;
591    }
592
593    // Host error if present on execution
594    // If ok, check contract creation limit and calculate gas deduction on output len.
595    //
596    // EIP-3541: Reject new contract code starting with the 0xEF byte
597    if !is_eip3541_disabled
598        && spec_id.is_enabled_in(LONDON)
599        && interpreter_result.output.first() == Some(&0xEF)
600    {
601        journal.checkpoint_revert(checkpoint);
602        interpreter_result.result = InstructionResult::CreateContractStartingWithEF;
603        return;
604    }
605
606    // regular gas for code deposit. It is zero in EIP-8037.
607    let gas_for_code = cfg
608        .gas_params()
609        .code_deposit_cost(interpreter_result.output.len());
610    if !interpreter_result.gas.record_regular_cost(gas_for_code) {
611        // Record code deposit gas cost and check if we are out of gas.
612        // EIP-2 point 3: If contract creation does not have enough gas to pay for the
613        // final gas fee for adding the contract code to the state, the contract
614        // creation fails (i.e. goes out-of-gas) rather than leaving an empty contract.
615        if spec_id.is_enabled_in(HOMESTEAD) {
616            journal.checkpoint_revert(checkpoint);
617            interpreter_result.result = InstructionResult::OutOfGas;
618            return;
619        } else {
620            interpreter_result.output = Bytes::new();
621        }
622    }
623
624    // EIP-8037: Hash cost for deployed bytecode (keccak256)
625    // HASH_COST(L) = 6 × ceil(L / 32)
626    // Both CREATE and CREATE2 must pay this cost: it covers hashing the deployed code
627    // to compute the code_hash stored in the account. CREATE2's existing keccak256 charge
628    // (in create2_cost) is for hashing the init code during address derivation, which is
629    // a different hash.
630    if cfg.is_amsterdam_eip8037_enabled() {
631        let hash_cost = cfg
632            .gas_params()
633            .keccak256_cost(interpreter_result.output.len());
634        if !interpreter_result.gas.record_regular_cost(hash_cost) {
635            journal.checkpoint_revert(checkpoint);
636            interpreter_result.result = InstructionResult::OutOfGas;
637            return;
638        }
639        // State gas for code deposit (EIP-8037).
640        // Charged after size check: only code that passes validation incurs state gas cost.
641        //
642        // Note: This should be last operation before checkpoint commit as spending state before this messes
643        // with refilling of state gas.
644        let state_gas_for_code = cfg
645            .gas_params()
646            .code_deposit_state_gas(interpreter_result.output.len());
647        if state_gas_for_code > 0 && !interpreter_result.gas.record_state_cost(state_gas_for_code) {
648            journal.checkpoint_revert(checkpoint);
649            interpreter_result.result = InstructionResult::OutOfGas;
650            return;
651        }
652    }
653
654    // If we have enough gas we can commit changes.
655    journal.checkpoint_commit();
656
657    // Do analysis of bytecode straight away.
658    let bytecode = Bytecode::new_legacy(interpreter_result.output.clone());
659
660    // Set code
661    journal.set_code(address, bytecode);
662
663    interpreter_result.result = InstructionResult::Return;
664}