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    keccak256, 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 fn is_finished(&self) -> bool {
88        self.is_finished
89    }
90
91    /// Sets the finished state of the frame.
92    pub 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    #[allow(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
301        let mut init_code_hash = None;
302        let created_address = match inputs.scheme() {
303            CreateScheme::Create => inputs.caller().create(old_nonce),
304            CreateScheme::Create2 { salt } => {
305                let init_code_hash = *init_code_hash.insert(keccak256(inputs.init_code()));
306                inputs.caller().create2(salt.to_be_bytes(), init_code_hash)
307            }
308            CreateScheme::Custom { address } => address,
309        };
310
311        drop(caller_info); // Drop caller info to avoid borrow checker issues.
312
313        // warm load account.
314        journal.load_account(created_address)?;
315
316        // Create account, transfer funds and make the journal checkpoint.
317        let checkpoint = match context.journal_mut().create_account_checkpoint(
318            inputs.caller(),
319            created_address,
320            inputs.value(),
321            spec,
322        ) {
323            Ok(checkpoint) => checkpoint,
324            Err(e) => return return_error(e.into()),
325        };
326
327        let bytecode = ExtBytecode::new_with_optional_hash(
328            Bytecode::new_legacy(inputs.init_code().clone()),
329            init_code_hash,
330        );
331
332        let interpreter_input = InputsImpl {
333            target_address: created_address,
334            caller_address: inputs.caller(),
335            bytecode_address: None,
336            input: CallInput::Bytes(Bytes::new()),
337            call_value: inputs.value(),
338        };
339        let gas_limit = inputs.gas_limit();
340
341        this.get(EthFrame::invalid).clear(
342            FrameData::Create(CreateFrame { created_address }),
343            FrameInput::Create(inputs),
344            depth,
345            memory,
346            bytecode,
347            interpreter_input,
348            false,
349            spec,
350            gas_limit,
351            reservoir_remaining_gas,
352            checkpoint,
353        );
354
355        Ok(ItemOrResult::Item(this.consume()))
356    }
357
358    /// Initializes a frame with the given context and precompiles.
359    pub fn init_with_context<
360        CTX: ContextTr,
361        PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
362    >(
363        this: OutFrame<'_, Self>,
364        ctx: &mut CTX,
365        precompiles: &mut PRECOMPILES,
366        frame_init: FrameInit,
367    ) -> Result<
368        ItemOrResult<FrameToken, FrameResult>,
369        ContextError<<<CTX as ContextTr>::Db as Database>::Error>,
370    > {
371        // TODO cleanup inner make functions
372        let FrameInit {
373            depth,
374            memory,
375            frame_input,
376        } = frame_init;
377
378        match frame_input {
379            FrameInput::Call(inputs) => {
380                Self::make_call_frame(this, ctx, precompiles, depth, memory, inputs)
381            }
382            FrameInput::Create(inputs) => Self::make_create_frame(this, ctx, depth, memory, inputs),
383            FrameInput::Empty => unreachable!(),
384        }
385    }
386}
387
388impl EthFrame<EthInterpreter> {
389    /// Processes the next interpreter action, either creating a new frame or returning a result.
390    pub fn process_next_action<
391        CTX: ContextTr,
392        ERROR: From<ContextTrDbError<CTX>> + FromStringError,
393    >(
394        &mut self,
395        context: &mut CTX,
396        next_action: InterpreterAction,
397    ) -> Result<FrameInitOrResult<Self>, ERROR> {
398        // Run interpreter
399
400        let mut interpreter_result = match next_action {
401            InterpreterAction::NewFrame(frame_input) => {
402                let depth = self.depth + 1;
403                return Ok(ItemOrResult::Item(FrameInit {
404                    frame_input,
405                    depth,
406                    memory: self.interpreter.memory.new_child_context(),
407                }));
408            }
409            InterpreterAction::Return(result) => result,
410        };
411
412        // Handle return from frame
413        let result = match &self.data {
414            FrameData::Call(frame) => {
415                // return_call
416                // Revert changes or not.
417                if interpreter_result.result.is_ok() {
418                    context.journal_mut().checkpoint_commit();
419                } else {
420                    context.journal_mut().checkpoint_revert(self.checkpoint);
421                }
422                ItemOrResult::Result(FrameResult::Call(CallOutcome::new(
423                    interpreter_result,
424                    frame.return_memory_range.clone(),
425                )))
426            }
427            FrameData::Create(frame) => {
428                let (cfg, journal) = context.cfg_journal_mut();
429                return_create(
430                    journal,
431                    cfg,
432                    self.checkpoint,
433                    &mut interpreter_result,
434                    frame.created_address,
435                );
436
437                ItemOrResult::Result(FrameResult::Create(CreateOutcome::new(
438                    interpreter_result,
439                    Some(frame.created_address),
440                )))
441            }
442        };
443
444        Ok(result)
445    }
446
447    /// Processes a frame result and updates the interpreter state accordingly.
448    pub fn return_result<CTX: ContextTr, ERROR: From<ContextTrDbError<CTX>> + FromStringError>(
449        &mut self,
450        ctx: &mut CTX,
451        result: FrameResult,
452    ) -> Result<(), ERROR> {
453        self.interpreter.memory.free_child_context();
454        take_error::<ERROR, _>(ctx.error())?;
455
456        // Insert result to the top frame.
457        match result {
458            FrameResult::Call(outcome) => {
459                let out_gas = outcome.gas();
460                let ins_result = *outcome.instruction_result();
461                let returned_len = outcome.result.output.len();
462
463                let interpreter = &mut self.interpreter;
464                let mem_length = outcome.memory_length();
465                let mem_start = outcome.memory_start();
466                interpreter.return_data.set_buffer(outcome.result.output);
467
468                let target_len = min(mem_length, returned_len);
469
470                if ins_result == InstructionResult::FatalExternalError {
471                    panic!("Fatal external error in insert_call_outcome");
472                }
473
474                let item = if ins_result.is_ok() {
475                    U256::from(1)
476                } else {
477                    U256::ZERO
478                };
479                // Safe to push without stack limit check
480                let _ = interpreter.stack.push(item);
481
482                // Return unspend gas.
483                if ins_result.is_ok_or_revert() {
484                    interpreter.gas.erase_cost(out_gas.remaining());
485                    interpreter
486                        .memory
487                        .set(mem_start, &interpreter.return_data.buffer()[..target_len]);
488                }
489
490                // handle reservoir remaining gas
491                handle_reservoir_remaining_gas(&mut interpreter.gas, &out_gas, ins_result);
492
493                if ins_result.is_ok() {
494                    interpreter.gas.record_refund(out_gas.refunded());
495                }
496            }
497            FrameResult::Create(outcome) => {
498                let instruction_result = *outcome.instruction_result();
499                let interpreter = &mut self.interpreter;
500
501                if instruction_result == InstructionResult::Revert {
502                    // Save data to return data buffer if the create reverted
503                    interpreter
504                        .return_data
505                        .set_buffer(outcome.output().to_owned());
506                } else {
507                    // Otherwise clear it. Note that RETURN opcode should abort.
508                    interpreter.return_data.clear();
509                };
510
511                assert_ne!(
512                    instruction_result,
513                    InstructionResult::FatalExternalError,
514                    "Fatal external error in insert_eofcreate_outcome"
515                );
516
517                let this_gas = &mut interpreter.gas;
518                // Refund unused gas for success and revert cases.
519                if instruction_result.is_ok_or_revert() {
520                    this_gas.erase_cost(outcome.gas().remaining());
521                }
522
523                // handle reservoir remaining gas
524                handle_reservoir_remaining_gas(this_gas, outcome.gas(), instruction_result);
525
526                let stack_item = if instruction_result.is_ok() {
527                    this_gas.record_refund(outcome.gas().refunded());
528                    outcome.address.unwrap_or_default().into_word().into()
529                } else {
530                    U256::ZERO
531                };
532
533                // Safe to push without stack limit check
534                let _ = interpreter.stack.push(stack_item);
535            }
536        }
537
538        Ok(())
539    }
540}
541
542/// Handles the remaining gas of the parent frame.
543#[inline]
544pub fn handle_reservoir_remaining_gas(
545    parent_gas: &mut Gas,
546    child_gas: &Gas,
547    result: InstructionResult,
548) {
549    if result.is_ok() {
550        // On success: parent takes the child's final reservoir.
551        parent_gas.set_reservoir(child_gas.reservoir());
552        // Accumulate child's state gas into parent's total.
553        // Parent may have already charged state gas (e.g., new_account + create) before
554        // creating the child frame. Child starts with state_gas_spent=0, so we must add
555        // rather than overwrite to preserve the parent's prior charges.
556        parent_gas.set_state_gas_spent(parent_gas.state_gas_spent() + child_gas.state_gas_spent());
557    } else {
558        // On revert or halt: state changes are undone, so ALL state gas returns
559        // to the parent's reservoir.
560        // - child.state_gas_spent(): state gas the child consumed (state rolled back, so refunded)
561        // - child.reservoir(): state gas the child didn't use (including gas returned from
562        //   deeper failed frames)
563        // This replaces (not adds to) the parent's reservoir because the child started with
564        // the parent's reservoir value (REVM doesn't zero it before the call), so the child's
565        // total already includes the parent's original reservoir.
566        parent_gas.set_reservoir(child_gas.state_gas_spent() + child_gas.reservoir());
567    }
568}
569
570/// Handles the result of a CREATE operation, including validation and state updates.
571pub fn return_create<JOURNAL: JournalTr, CFG: Cfg>(
572    journal: &mut JOURNAL,
573    cfg: CFG,
574    checkpoint: JournalCheckpoint,
575    interpreter_result: &mut InterpreterResult,
576    address: Address,
577) {
578    let max_code_size = cfg.max_code_size();
579    let is_eip3541_disabled = cfg.is_eip3541_disabled();
580    let spec_id = cfg.spec().into();
581
582    // If return is not ok revert and return.
583    if !interpreter_result.result.is_ok() {
584        journal.checkpoint_revert(checkpoint);
585        return;
586    }
587
588    // EIP-170: Contract code size limit to 0x6000 (~25kb)
589    // EIP-7954 increased this limit to 0x8000 (~32kb).
590    // This must be checked BEFORE charging state gas for code deposit,
591    // so that oversized code does not incur storage gas costs.
592    if spec_id.is_enabled_in(SPURIOUS_DRAGON) && interpreter_result.output.len() > max_code_size {
593        journal.checkpoint_revert(checkpoint);
594        interpreter_result.result = InstructionResult::CreateContractSizeLimit;
595        return;
596    }
597
598    // Host error if present on execution
599    // If ok, check contract creation limit and calculate gas deduction on output len.
600    //
601    // EIP-3541: Reject new contract code starting with the 0xEF byte
602    if !is_eip3541_disabled
603        && spec_id.is_enabled_in(LONDON)
604        && interpreter_result.output.first() == Some(&0xEF)
605    {
606        journal.checkpoint_revert(checkpoint);
607        interpreter_result.result = InstructionResult::CreateContractStartingWithEF;
608        return;
609    }
610
611    // regular gas for code deposit. It is zero in EIP-8037.
612    let gas_for_code = cfg
613        .gas_params()
614        .code_deposit_cost(interpreter_result.output.len());
615    if !interpreter_result.gas.record_regular_cost(gas_for_code) {
616        // Record code deposit gas cost and check if we are out of gas.
617        // EIP-2 point 3: If contract creation does not have enough gas to pay for the
618        // final gas fee for adding the contract code to the state, the contract
619        // creation fails (i.e. goes out-of-gas) rather than leaving an empty contract.
620        if spec_id.is_enabled_in(HOMESTEAD) {
621            journal.checkpoint_revert(checkpoint);
622            interpreter_result.result = InstructionResult::OutOfGas;
623            return;
624        } else {
625            interpreter_result.output = Bytes::new();
626        }
627    }
628
629    // EIP-8037: Hash cost for deployed bytecode (keccak256)
630    // HASH_COST(L) = 6 × ceil(L / 32)
631    // Both CREATE and CREATE2 must pay this cost: it covers hashing the deployed code
632    // to compute the code_hash stored in the account. CREATE2's existing keccak256 charge
633    // (in create2_cost) is for hashing the init code during address derivation, which is
634    // a different hash.
635    if cfg.is_amsterdam_eip8037_enabled() {
636        let hash_cost = cfg
637            .gas_params()
638            .keccak256_cost(interpreter_result.output.len());
639        if !interpreter_result.gas.record_regular_cost(hash_cost) {
640            journal.checkpoint_revert(checkpoint);
641            interpreter_result.result = InstructionResult::OutOfGas;
642            return;
643        }
644        // State gas for code deposit (EIP-8037).
645        // Charged after size check: only code that passes validation incurs state gas cost.
646        //
647        // Note: This should be last operation before checkpoint commit as spending state before this messes
648        // with refilling of state gas.
649        let state_gas_for_code = cfg
650            .gas_params()
651            .code_deposit_state_gas(interpreter_result.output.len());
652        if state_gas_for_code > 0 && !interpreter_result.gas.record_state_cost(state_gas_for_code) {
653            journal.checkpoint_revert(checkpoint);
654            interpreter_result.result = InstructionResult::OutOfGas;
655            return;
656        }
657    }
658
659    // If we have enough gas we can commit changes.
660    journal.checkpoint_commit();
661
662    // Do analysis of bytecode straight away.
663    let bytecode = Bytecode::new_legacy(interpreter_result.output.clone());
664
665    // Set code
666    journal.set_code(address, bytecode);
667
668    interpreter_result.result = InstructionResult::Return;
669}