Skip to main content

revm_handler/
execution.rs

1use context::{ContextTr, Database, JournalTr};
2use context_interface::{
3    journaled_state::{account::JournaledAccountTr, JournalCheckpoint, JournalLoadError},
4    Cfg, Transaction,
5};
6use interpreter::{
7    CallInput, CallInputs, CallScheme, CallValue, CreateInputs, CreateScheme, FrameInput,
8    GasTracker,
9};
10use primitives::TxKind;
11use state::Bytecode;
12use std::boxed::Box;
13
14/// Creates the first [`FrameInput`] from the transaction and the
15/// transaction-level `gas`, forwarding all remaining regular gas and the
16/// reservoir to the frame.
17///
18/// Under EIP-2780 this completes the runtime gas phase started at
19/// pre-execution (the authorization charges): the target is loaded once and
20/// its state-dependent charges are recorded on `gas`:
21///
22/// - A value transfer to a non-existent recipient grows the state by one
23///   account leaf and pays the new-account state gas. The charge stays
24///   deducted and only the decision travels on the frame inputs
25///   (`charged_new_account_state_gas`), so the handler refunds it at frame
26///   settle when no account leaf ends up created. Checked before the
27///   delegation resolution, matching the spec's `prepare_dispatch` order.
28/// - An EIP-7702 delegated recipient pays the delegation-target access
29///   following the standard EIP-2929 warm/cold model. The charge must be
30///   covered before the delegate is read, so an out-of-gas here leaves a cold
31///   delegate out of the EIP-7928 block access list.
32/// - A create transaction whose deployment target does not already exist pays
33///   the account-creation state gas (`charged_create_state_gas`), decided by
34///   the single read that also warms the target.
35///
36/// The recipient is evaluated after the authorizations were applied, so a
37/// recipient materialized or delegated by an authorization in this
38/// transaction is seen in its post-authorization state. A self-transfer
39/// recipient is the (non-empty) sender, so the new-account charge never fires
40/// for it, while a delegated sender still pays for resolving its delegation.
41///
42/// Returns `None` when the charges run out of gas: the transaction stays
43/// valid but must be included as an out-of-gas halt without entering
44/// execution ([`runtime_oog_unwind`]).
45#[inline]
46pub fn create_init_frame<CTX: ContextTr>(
47    ctx: &mut CTX,
48    gas: &mut GasTracker,
49) -> Result<Option<FrameInput>, <<CTX::Journal as JournalTr>::Database as Database>::Error> {
50    let is_eip2780 = ctx.cfg().is_amsterdam_eip2780_enabled();
51    let params = ctx.cfg().gas_params();
52    let new_account_state_gas = params.new_account_state_gas();
53    let create_state_gas = params.create_state_gas();
54    let warm_access_cost = params.warm_storage_read_cost();
55    let cold_account_additional_cost = params.cold_account_additional_cost();
56    let (tx, journal) = ctx.tx_journal_mut();
57    let input = tx.input().clone();
58
59    match tx.kind() {
60        TxKind::Call(target_address) => {
61            // Load the recipient once (its access was already charged at the
62            // cold rate at the intrinsic phase).
63            let account = &journal.load_account_with_code(target_address)?.info;
64            let recipient_is_empty = account.is_empty();
65            let delegated_address = account.code.as_ref().and_then(Bytecode::eip7702_address);
66            let mut known_bytecode = (
67                account.code_hash(),
68                account.code.clone().unwrap_or_default(),
69            );
70
71            let mut charged_new_account_state_gas = false;
72            if is_eip2780 && !tx.value().is_zero() && recipient_is_empty {
73                if !gas.record_state_cost(new_account_state_gas) {
74                    return Ok(None);
75                }
76                charged_new_account_state_gas = true;
77            }
78
79            if let Some(delegated_address) = delegated_address {
80                if is_eip2780 {
81                    // Charge the warm access upfront and the cold difference
82                    // after the load. Charges made before an out-of-gas bail
83                    // need no undo: the runtime out-of-gas path rebuilds the
84                    // transaction-level gas.
85                    if !gas.record_regular_cost(warm_access_cost) {
86                        return Ok(None);
87                    }
88                    let skip_cold_load = gas.remaining() < cold_account_additional_cost;
89                    let acc = match journal.load_account_info_skip_cold_load(
90                        delegated_address,
91                        true,
92                        skip_cold_load,
93                    ) {
94                        Ok(acc) => acc,
95                        Err(JournalLoadError::ColdLoadSkipped) => return Ok(None),
96                        Err(JournalLoadError::DBError(e)) => return Err(e),
97                    };
98
99                    if acc.is_cold && !gas.record_regular_cost(cold_account_additional_cost) {
100                        return Ok(None);
101                    }
102                    known_bytecode = (acc.code_hash(), acc.code.clone().unwrap_or_default());
103                } else {
104                    let account = &journal.load_account_with_code(delegated_address)?.info;
105                    known_bytecode = (
106                        account.code_hash(),
107                        account.code.clone().unwrap_or_default(),
108                    );
109                }
110            }
111
112            Ok(Some(FrameInput::Call(Box::new(CallInputs {
113                input: CallInput::Bytes(input),
114                gas_limit: gas.remaining(),
115                target_address,
116                bytecode_address: target_address,
117                known_bytecode,
118                caller: tx.caller(),
119                value: CallValue::Transfer(tx.value()),
120                scheme: CallScheme::Call,
121                is_static: false,
122                return_memory_offset: 0..0,
123                reservoir: gas.reservoir(),
124                charged_new_account_state_gas,
125            }))))
126        }
127        TxKind::Create => {
128            let mut charged_create_state_gas = false;
129            if is_eip2780 {
130                // The tx nonce was validated against the caller's nonce, which
131                // a create transaction bumps only at frame creation — after
132                // this point.
133                let created_address = tx.caller().create(tx.nonce());
134                let target_is_empty = journal.load_account(created_address)?.info.is_empty();
135                if target_is_empty {
136                    if !gas.record_state_cost(create_state_gas) {
137                        return Ok(None);
138                    }
139                    charged_create_state_gas = true;
140                }
141            }
142            let mut inputs = CreateInputs::new(
143                tx.caller(),
144                CreateScheme::Create,
145                tx.value(),
146                input,
147                gas.remaining(),
148                gas.reservoir(),
149            );
150            inputs.set_charged_create_state_gas(charged_create_state_gas);
151            Ok(Some(FrameInput::Create(Box::new(inputs))))
152        }
153    }
154}
155
156/// Unwinds the EIP-2780 runtime gas phase after first-frame creation ran out
157/// of gas ([`create_init_frame`] returned `None`): reverts the phase's
158/// journal checkpoint — dropping all runtime state changes, including applied
159/// EIP-7702 delegations — and bumps the sender nonce of a create transaction,
160/// whose increment precedes message processing and survives the halt. (Calls
161/// bump it in `validate_against_state_and_deduct_caller`; creates at frame
162/// creation, which the runtime halt never reaches.)
163#[inline]
164pub fn runtime_oog_unwind<CTX: ContextTr>(
165    ctx: &mut CTX,
166    checkpoint: JournalCheckpoint,
167) -> Result<(), <<CTX::Journal as JournalTr>::Database as Database>::Error> {
168    let (tx, journal) = ctx.tx_journal_mut();
169    journal.checkpoint_revert(checkpoint);
170    if tx.kind().is_create() {
171        journal.load_account_mut(tx.caller())?.data.bump_nonce();
172    }
173    Ok(())
174}