Skip to main content

revm_handler/
post_execution.rs

1use crate::FrameResult;
2use context::journaled_state::account::JournaledAccountTr;
3use context_interface::{
4    journaled_state::JournalTr,
5    result::{ExecutionResult, HaltReason, HaltReasonTr, ResultGas},
6    Block, Cfg, ContextTr, Database, LocalContextTr, Transaction,
7};
8use interpreter::{Gas, InitialAndFloorGas, SuccessOrHalt};
9use primitives::{hardfork::SpecId, U256};
10
11/// Builds a [`ResultGas`] from the execution [`Gas`] struct and [`InitialAndFloorGas`].
12pub fn build_result_gas(gas: &Gas, init_and_floor_gas: InitialAndFloorGas) -> ResultGas {
13    let state_gas = gas
14        .state_gas_spent()
15        .saturating_add(init_and_floor_gas.initial_state_gas)
16        .saturating_sub(init_and_floor_gas.eip7702_reservoir_refund);
17
18    ResultGas::default()
19        .with_total_gas_spent(
20            gas.limit()
21                .saturating_sub(gas.remaining())
22                .saturating_sub(gas.reservoir()),
23        )
24        .with_refunded(gas.refunded() as u64)
25        .with_floor_gas(init_and_floor_gas.floor_gas)
26        .with_state_gas_spent(state_gas)
27}
28
29/// Ensures minimum gas floor is spent according to EIP-7623.
30///
31/// Per EIP-8037, gas used before refund is `tx.gas - gas_left - state_gas_reservoir`.
32/// The floor applies to this combined total, not just regular gas.
33pub const fn eip7623_check_gas_floor(gas: &mut Gas, init_and_floor_gas: InitialAndFloorGas) {
34    // EIP-7623: Increase calldata cost
35    // EIP-8037: tx_gas_used_before_refund = tx.gas - gas_left - reservoir
36    // The floor must apply to this combined value, not just (limit - remaining).
37    let gas_used_before_refund = gas.total_gas_spent().saturating_sub(gas.reservoir());
38    let gas_used_after_refund = gas_used_before_refund.saturating_sub(gas.refunded() as u64);
39    if gas_used_after_refund < init_and_floor_gas.floor_gas {
40        // Set spent so that (limit - remaining - reservoir) = floor_gas
41        // i.e. remaining = limit - floor_gas - reservoir
42        gas.set_spent(init_and_floor_gas.floor_gas + gas.reservoir());
43        // clear refund
44        gas.set_refund(0);
45    }
46}
47
48/// Calculates and applies gas refunds based on the specification.
49pub fn refund(spec: SpecId, gas: &mut Gas, eip7702_refund: i64) {
50    gas.record_refund(eip7702_refund);
51    // Calculate gas refund for transaction.
52    // If spec is set to london, it will decrease the maximum refund amount to 5th part of
53    // gas spend. (Before london it was 2th part of gas spend)
54    gas.set_final_refund(spec.is_enabled_in(SpecId::LONDON));
55}
56
57/// Reimburses the caller for unused gas.
58#[inline]
59pub fn reimburse_caller<CTX: ContextTr>(
60    context: &mut CTX,
61    gas: &Gas,
62    additional_refund: U256,
63) -> Result<(), <CTX::Db as Database>::Error> {
64    // If fee charge was disabled (e.g. eth_call simulations), no gas was
65    // deducted from the caller upfront so there is nothing to reimburse.
66    if context.cfg().is_fee_charge_disabled() {
67        return Ok(());
68    }
69    let basefee = context.block().basefee() as u128;
70    let caller = context.tx().caller();
71    let effective_gas_price = context.tx().effective_gas_price(basefee);
72
73    // Return balance of not spent gas.
74    // Include reservoir gas (EIP-8037) which is also unused and must be reimbursed.
75    let reimbursable = gas.remaining() + gas.reservoir() + gas.refunded() as u64;
76    context
77        .journal_mut()
78        .load_account_mut(caller)?
79        .incr_balance(
80            U256::from(effective_gas_price.saturating_mul(reimbursable as u128))
81                + additional_refund,
82        );
83
84    Ok(())
85}
86
87/// Rewards the beneficiary with transaction fees.
88#[inline]
89pub fn reward_beneficiary<CTX: ContextTr>(
90    context: &mut CTX,
91    gas: &Gas,
92) -> Result<(), <CTX::Db as Database>::Error> {
93    // If fee charge was disabled (e.g. eth_call simulations), the caller was
94    // never charged for gas so there are no fees to transfer to the beneficiary.
95    if context.cfg().is_fee_charge_disabled() {
96        return Ok(());
97    }
98    let (block, tx, cfg, journal, _, _) = context.all_mut();
99    let basefee = block.basefee() as u128;
100    let effective_gas_price = tx.effective_gas_price(basefee);
101
102    // Transfer fee to coinbase/beneficiary.
103    // EIP-1559 discard basefee for coinbase transfer. Basefee amount of gas is discarded.
104    let coinbase_gas_price = if cfg.spec().into().is_enabled_in(SpecId::LONDON) {
105        effective_gas_price.saturating_sub(basefee)
106    } else {
107        effective_gas_price
108    };
109
110    // Reward beneficiary.
111    // Exclude reservoir gas (EIP-8037) from the used gas — reservoir is unused and reimbursed.
112    let effective_used = gas.used().saturating_sub(gas.reservoir());
113    journal
114        .load_account_mut(block.beneficiary())?
115        .incr_balance(U256::from(coinbase_gas_price * effective_used as u128));
116
117    Ok(())
118}
119
120/// Calculate last gas spent and transform internal reason to external.
121///
122/// TODO make Journal FinalOutput more generic.
123pub fn output<CTX: ContextTr<Journal: JournalTr>, HALTREASON: HaltReasonTr>(
124    context: &mut CTX,
125    // TODO, make this more generic and nice.
126    // FrameResult should be a generic that returns gas and interpreter result.
127    result: FrameResult,
128    result_gas: ResultGas,
129) -> ExecutionResult<HALTREASON> {
130    let output = result.output();
131    let instruction_result = result.into_interpreter_result();
132
133    // take logs from journal.
134    let logs = context.journal_mut().take_logs();
135
136    match SuccessOrHalt::<HALTREASON>::from(instruction_result.result) {
137        SuccessOrHalt::Success(reason) => ExecutionResult::Success {
138            reason,
139            gas: result_gas,
140            logs,
141            output,
142        },
143        SuccessOrHalt::Revert => ExecutionResult::Revert {
144            gas: result_gas,
145            logs,
146            output: output.into_data(),
147        },
148        SuccessOrHalt::Halt(reason) => {
149            // Bubble up precompile errors from context when available
150            if matches!(
151                instruction_result.result,
152                interpreter::InstructionResult::PrecompileError
153            ) {
154                if let Some(message) = context.local_mut().take_precompile_error_context() {
155                    return ExecutionResult::Halt {
156                        reason: HALTREASON::from(HaltReason::PrecompileErrorWithContext(message)),
157                        gas: result_gas,
158                        logs,
159                    };
160                }
161            }
162            ExecutionResult::Halt {
163                reason,
164                gas: result_gas,
165                logs,
166            }
167        }
168        // Only two internal return flags.
169        flag @ (SuccessOrHalt::FatalExternalError | SuccessOrHalt::Internal(_)) => {
170            panic!(
171                "Encountered unexpected internal return flag: {flag:?} with instruction result: {instruction_result:?}"
172            )
173        }
174    }
175}