Skip to main content

revm_context_interface/
result.rs

1//! Result of the EVM execution. Containing both execution result, state and errors.
2//!
3//! [`ExecutionResult`] is the result of the EVM execution.
4//!
5//! [`InvalidTransaction`] is the error that is returned when the transaction is invalid.
6//!
7//! [`InvalidHeader`] is the error that is returned when the header is invalid.
8//!
9//! [`SuccessReason`] is the reason that the transaction successfully completed.
10use crate::{context::ContextError, transaction::TransactionError};
11use core::fmt::{self, Debug};
12use database_interface::DBErrorMarker;
13use primitives::{Address, Bytes, Log, U256};
14use state::EvmState;
15use std::{borrow::Cow, boxed::Box, string::String, sync::Arc, vec::Vec};
16
17/// Trait for the halt reason.
18pub trait HaltReasonTr: Clone + Debug + PartialEq + Eq + From<HaltReason> {}
19
20impl<T> HaltReasonTr for T where T: Clone + Debug + PartialEq + Eq + From<HaltReason> {}
21
22/// Tuple containing evm execution result and state.s
23#[derive(Clone, Debug, PartialEq, Eq, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct ExecResultAndState<R, S = EvmState> {
26    /// Execution result
27    pub result: R,
28    /// Output State.
29    pub state: S,
30}
31
32/// Type alias for backwards compatibility.
33pub type ResultAndState<H = HaltReason, S = EvmState> = ExecResultAndState<ExecutionResult<H>, S>;
34
35/// Tuple containing multiple execution results and state.
36pub type ResultVecAndState<R, S> = ExecResultAndState<Vec<R>, S>;
37
38impl<R, S> ExecResultAndState<R, S> {
39    /// Creates new ResultAndState.
40    pub const fn new(result: R, state: S) -> Self {
41        Self { result, state }
42    }
43}
44
45/// Gas accounting result from transaction execution.
46///
47/// Self-contained gas snapshot with all values needed for downstream consumers.
48///
49/// ## Stored values
50///
51/// | Getter                 | Source                             | Description                                    |
52/// |------------------------|------------------------------------|------------------------------------------------|
53/// | [`total_gas_spent()`]  | `Gas::spent()` = limit − remaining | Total gas consumed before refund               |
54/// | [`inner_refunded()`]   | `Gas::refunded()` as u64           | Gas refunded (capped per EIP-3529)             |
55/// | [`floor_gas()`]        | `InitialAndFloorGas::floor_gas`    | EIP-7623 floor gas (0 if not applicable)       |
56/// | [`state_gas_spent_final()`] | `Gas::state_gas_spent`        | State gas consumed during execution (EIP-8037) |
57///
58/// [`total_gas_spent()`]: ResultGas::total_gas_spent
59/// [`inner_refunded()`]: ResultGas::inner_refunded
60/// [`floor_gas()`]: ResultGas::floor_gas
61/// [`state_gas_spent_final()`]: ResultGas::state_gas_spent_final
62///
63/// ## Derived values
64///
65/// - [`tx_gas_used()`](ResultGas::tx_gas_used) = `max(total_gas_spent − refunded, floor_gas)` (the value that goes into receipts)
66/// - [`block_regular_gas_used()`](ResultGas::block_regular_gas_used) = `max(total_gas_spent − state_gas_spent, floor_gas)`
67///   (EIP-8037 + EIP-7778: pre-refund; the EIP-7623 calldata floor binds against the regular component)
68/// - [`block_state_gas_used()`](ResultGas::block_state_gas_used) = `state_gas_spent`
69/// - [`spent_sub_refunded()`](ResultGas::spent_sub_refunded) = `total_gas_spent − refunded` (before floor gas check)
70/// - [`final_refunded()`](ResultGas::final_refunded) = `refunded` when floor gas is inactive, `0` when floor gas kicks in
71#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73pub struct ResultGas {
74    /// Total gas spent consisting of regular and state gas.
75    /// For actual gas used, use [`used()`](ResultGas::used).
76    #[cfg_attr(feature = "serde", serde(rename = "gas_spent"))]
77    total_gas_spent: u64,
78    /// State gas consumed during execution (EIP-8037), net of the EIP-7702
79    /// per-authorization state-gas refund applied at result-build time.
80    /// Tracks gas for storage creation, account creation, and code deposit.
81    /// Zero when state gas is not enabled.
82    #[cfg_attr(feature = "serde", serde(default))]
83    state_gas_spent: u64,
84    /// Gas refund amount (capped per EIP-3529).
85    ///
86    /// Note: This is the raw refund before EIP-7623 floor gas adjustment.
87    /// Use [`final_refunded()`](ResultGas::final_refunded) for the effective refund.
88    #[cfg_attr(feature = "serde", serde(rename = "gas_refunded"))]
89    refunded: u64,
90    /// EIP-7623 floor gas. Zero when not applicable.
91    floor_gas: u64,
92}
93
94impl ResultGas {
95    /****** Constructor functions *****/
96
97    /// Creates a new `ResultGas`.
98    #[inline]
99    #[deprecated(
100        since = "32.0.0",
101        note = "It can be a footgun as gas limit is removed, use ResultGas::with_* functions instead"
102    )]
103    pub const fn new(total_gas_spent: u64, refunded: u64, floor_gas: u64) -> Self {
104        Self {
105            total_gas_spent,
106            refunded,
107            floor_gas,
108            state_gas_spent: 0,
109        }
110    }
111
112    /// Creates a new `ResultGas` with state gas tracking.
113    #[inline]
114    pub const fn new_with_state_gas(
115        total_gas_spent: u64,
116        refunded: u64,
117        floor_gas: u64,
118        state_gas_spent: u64,
119    ) -> Self {
120        Self {
121            total_gas_spent,
122            refunded,
123            floor_gas,
124            state_gas_spent,
125        }
126    }
127
128    /****** Simple getters *****/
129
130    /// Returns the total gas spent inside execution before any refund.
131    ///
132    /// If you want final gas used, use [`used()`](ResultGas::used).
133    #[inline]
134    pub const fn total_gas_spent(&self) -> u64 {
135        self.total_gas_spent
136    }
137
138    /// Returns the final state gas spent during execution (EIP-8037).
139    ///
140    /// The stored value is already net of the EIP-7702 per-authorization
141    /// state-gas refund (subtracted when the result is built).
142    ///
143    /// This is same as [`ResultGas::block_state_gas_used`] for the transaction.
144    #[inline]
145    pub const fn state_gas_spent_final(&self) -> u64 {
146        self.state_gas_spent
147    }
148
149    /// Returns the EIP-7623 floor gas.
150    #[inline]
151    pub const fn floor_gas(&self) -> u64 {
152        self.floor_gas
153    }
154
155    /// Returns the raw refund from EVM execution, before EIP-7623 floor gas adjustment.
156    ///
157    /// This is the `refunded` field value (capped per EIP-3529 but not adjusted for floor gas).
158    /// See [`final_refunded()`](ResultGas::final_refunded) for the effective refund.
159    #[inline]
160    pub const fn inner_refunded(&self) -> u64 {
161        self.refunded
162    }
163
164    /// Returns the total gas spent.
165    #[inline]
166    #[deprecated(
167        since = "32.0.0",
168        note = "After EIP-8037 gas is split on
169    regular and state gas, this method is no longer valid.
170    Use [`ResultGas::total_gas_spent`] instead"
171    )]
172    pub const fn spent(&self) -> u64 {
173        self.total_gas_spent()
174    }
175
176    /****** Simple setters *****/
177
178    /// Sets the `total_gas_spent` field by mutable reference.
179    #[inline]
180    pub const fn set_total_gas_spent(&mut self, total_gas_spent: u64) {
181        self.total_gas_spent = total_gas_spent;
182    }
183
184    /// Sets the `refunded` field by mutable reference.
185    #[inline]
186    pub const fn set_refunded(&mut self, refunded: u64) {
187        self.refunded = refunded;
188    }
189
190    /// Sets the `floor_gas` field by mutable reference.
191    #[inline]
192    pub const fn set_floor_gas(&mut self, floor_gas: u64) {
193        self.floor_gas = floor_gas;
194    }
195
196    /// Sets the `state_gas_spent` field by mutable reference.
197    #[inline]
198    pub const fn set_state_gas_spent(&mut self, state_gas_spent: u64) {
199        self.state_gas_spent = state_gas_spent;
200    }
201
202    /// Sets the `spent` field by mutable reference.
203    #[inline]
204    #[deprecated(
205        since = "32.0.0",
206        note = "After EIP-8037 gas is split on
207            regular and state gas, this method is no longer valid.
208            Use [`ResultGas::set_total_gas_spent`] instead"
209    )]
210    pub const fn set_spent(&mut self, spent: u64) {
211        self.total_gas_spent = spent;
212    }
213
214    /****** Builder with_* methods *****/
215
216    /// Sets the `total_gas_spent` field.
217    #[inline]
218    pub const fn with_total_gas_spent(mut self, total_gas_spent: u64) -> Self {
219        self.total_gas_spent = total_gas_spent;
220        self
221    }
222
223    /// Sets the `refunded` field.
224    #[inline]
225    pub const fn with_refunded(mut self, refunded: u64) -> Self {
226        self.refunded = refunded;
227        self
228    }
229
230    /// Sets the `floor_gas` field.
231    #[inline]
232    pub const fn with_floor_gas(mut self, floor_gas: u64) -> Self {
233        self.floor_gas = floor_gas;
234        self
235    }
236
237    /// Sets the `state_gas_spent` field.
238    #[inline]
239    pub const fn with_state_gas_spent(mut self, state_gas_spent: u64) -> Self {
240        self.state_gas_spent = state_gas_spent;
241        self
242    }
243
244    /// Sets the `spent` field.
245    #[inline]
246    #[deprecated(
247        since = "32.0.0",
248        note = "After EIP-8037 gas is split on
249    regular and state gas, this method is no longer valid.
250    Use [`ResultGas::with_total_gas_spent`] instead"
251    )]
252    pub const fn with_spent(mut self, spent: u64) -> Self {
253        self.total_gas_spent = spent;
254        self
255    }
256
257    /* Aggregated getters */
258
259    /// Returns the total gas used by the transaction.
260    ///
261    /// This value is set inside Receipt.
262    #[inline]
263    pub const fn tx_gas_used(&self) -> u64 {
264        // consiste of regular and state gas.
265        let total_gas_spent = self.total_gas_spent();
266        // from total gas subtract the refunded gas. Refunded is capped by 20% of total gas spent.
267        let tx_gas_refunded = total_gas_spent.saturating_sub(self.inner_refunded());
268        max(tx_gas_refunded, self.floor_gas())
269    }
270
271    /// Returns the regular gas used by the block per EIP-8037 + EIP-7778.
272    ///
273    /// `max(total_gas_spent - state_gas_spent, floor_gas)` (pre-refund). The
274    /// EIP-7623 calldata floor binds against the regular component only (it is
275    /// not discounted by state gas), while the refund only affects
276    /// `tx_gas_used` (the receipt value), not the block-level regular
277    /// component.
278    #[inline]
279    pub const fn block_regular_gas_used(&self) -> u64 {
280        max(
281            self.total_gas_spent()
282                .saturating_sub(self.state_gas_spent_final()),
283            self.floor_gas(),
284        )
285    }
286
287    /// Returns the state gas used by the block.
288    ///
289    /// This is same as [`ResultGas::state_gas_spent_final`] for the block.
290    #[inline]
291    pub const fn block_state_gas_used(&self) -> u64 {
292        self.state_gas_spent_final()
293    }
294
295    /// Returns the final gas used: `max(spent - refunded, floor_gas)`.
296    ///
297    /// This is the value used for receipt `cumulative_gas_used` accumulation
298    /// and the per-transaction gas charge.
299    #[inline]
300    #[deprecated(
301        since = "32.0.0",
302        note = "Used is not descriptive enough, use [`ResultGas::tx_gas_used`] instead"
303    )]
304    pub const fn used(&self) -> u64 {
305        // EIP-7623: Increase calldata cost
306        // spend at least a gas_floor amount of gas.
307        let spent_sub_refunded = self.spent_sub_refunded();
308        if spent_sub_refunded < self.floor_gas {
309            return self.floor_gas;
310        }
311        spent_sub_refunded
312    }
313
314    /// Returns the gas spent minus the refunded gas.
315    ///
316    /// This does not take into account EIP-7623 floor gas. If you want to get the gas used in
317    /// receipt, use [`used()`](ResultGas::used) instead.
318    #[inline]
319    pub const fn spent_sub_refunded(&self) -> u64 {
320        self.total_gas_spent().saturating_sub(self.refunded)
321    }
322
323    /// Returns the effective refund after EIP-7623 floor gas adjustment.
324    ///
325    /// When floor gas kicks in (`spent - refunded < floor_gas`), the refund is zero
326    /// because the floor gas charge absorbs it entirely. Otherwise returns the raw refund.
327    #[inline]
328    pub const fn final_refunded(&self) -> u64 {
329        if self.spent_sub_refunded() < self.floor_gas {
330            0
331        } else {
332            self.refunded
333        }
334    }
335}
336
337/// Const function that returns the maximum of two u64 values.
338#[inline(always)]
339pub const fn max(a: u64, b: u64) -> u64 {
340    if a > b {
341        a
342    } else {
343        b
344    }
345}
346
347/// Const function that returns the minimum of two u64 values.
348#[inline(always)]
349pub const fn min(a: u64, b: u64) -> u64 {
350    if a < b {
351        a
352    } else {
353        b
354    }
355}
356
357impl fmt::Display for ResultGas {
358    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359        write!(
360            f,
361            "Gas used: {}, total spent: {}",
362            self.tx_gas_used(),
363            self.total_gas_spent()
364        )?;
365        if self.refunded > 0 {
366            write!(f, ", refunded: {}", self.refunded)?;
367        }
368        if self.floor_gas > 0 {
369            write!(f, ", floor: {}", self.floor_gas)?;
370        }
371        if self.state_gas_spent > 0 {
372            write!(f, ", state_gas: {}", self.state_gas_spent)?;
373        }
374        Ok(())
375    }
376}
377
378/// Result of a transaction execution
379#[derive(Clone, Debug, PartialEq, Eq, Hash)]
380#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
381pub enum ExecutionResult<HaltReasonTy = HaltReason> {
382    /// Returned successfully
383    Success {
384        /// Reason for the success.
385        reason: SuccessReason,
386        /// Gas accounting for the transaction.
387        gas: ResultGas,
388        /// Logs emitted by the transaction.
389        logs: Vec<Log>,
390        /// Output of the transaction.
391        output: Output,
392    },
393    /// Reverted by `REVERT` opcode that doesn't spend all gas
394    Revert {
395        /// Gas accounting for the transaction.
396        gas: ResultGas,
397        /// Logs emitted before the revert.
398        logs: Vec<Log>,
399        /// Output of the transaction.
400        output: Bytes,
401    },
402    /// Reverted for various reasons and spend all gas
403    Halt {
404        /// Reason for the halt.
405        reason: HaltReasonTy,
406        /// Gas accounting for the transaction.
407        ///
408        /// For standard EVM halts, gas used typically equals the gas limit.
409        /// Some system- or L2-specific halts may intentionally report less gas used.
410        gas: ResultGas,
411        /// Logs emitted before the halt.
412        logs: Vec<Log>,
413    },
414}
415
416impl<HaltReasonTy> ExecutionResult<HaltReasonTy> {
417    /// Returns if transaction execution is successful.
418    ///
419    /// 1 indicates success, 0 indicates revert.
420    ///
421    /// <https://eips.ethereum.org/EIPS/eip-658>
422    pub const fn is_success(&self) -> bool {
423        matches!(self, Self::Success { .. })
424    }
425
426    /// Maps a `DBError` to a new error type using the provided closure, leaving other variants unchanged.
427    pub fn map_haltreason<F, OHR>(self, op: F) -> ExecutionResult<OHR>
428    where
429        F: FnOnce(HaltReasonTy) -> OHR,
430    {
431        match self {
432            Self::Success {
433                reason,
434                gas,
435                logs,
436                output,
437            } => ExecutionResult::Success {
438                reason,
439                gas,
440                logs,
441                output,
442            },
443            Self::Revert { gas, logs, output } => ExecutionResult::Revert { gas, logs, output },
444            Self::Halt { reason, gas, logs } => ExecutionResult::Halt {
445                reason: op(reason),
446                gas,
447                logs,
448            },
449        }
450    }
451
452    /// Returns created address if execution is Create transaction
453    /// and Contract was created.
454    pub fn created_address(&self) -> Option<Address> {
455        match self {
456            Self::Success { output, .. } => output.address().cloned(),
457            _ => None,
458        }
459    }
460
461    /// Returns true if execution result is a Halt.
462    pub const fn is_halt(&self) -> bool {
463        matches!(self, Self::Halt { .. })
464    }
465
466    /// Returns the output data of the execution.
467    ///
468    /// Returns [`None`] if the execution was halted.
469    pub const fn output(&self) -> Option<&Bytes> {
470        match self {
471            Self::Success { output, .. } => Some(output.data()),
472            Self::Revert { output, .. } => Some(output),
473            _ => None,
474        }
475    }
476
477    /// Consumes the type and returns the output data of the execution.
478    ///
479    /// Returns [`None`] if the execution was halted.
480    pub fn into_output(self) -> Option<Bytes> {
481        match self {
482            Self::Success { output, .. } => Some(output.into_data()),
483            Self::Revert { output, .. } => Some(output),
484            _ => None,
485        }
486    }
487
488    /// Returns the logs emitted during execution.
489    pub const fn logs(&self) -> &[Log] {
490        match self {
491            Self::Success { logs, .. } | Self::Revert { logs, .. } | Self::Halt { logs, .. } => {
492                logs.as_slice()
493            }
494        }
495    }
496
497    /// Consumes [`self`] and returns the logs emitted during execution.
498    pub fn into_logs(self) -> Vec<Log> {
499        match self {
500            Self::Success { logs, .. } | Self::Revert { logs, .. } | Self::Halt { logs, .. } => {
501                logs
502            }
503        }
504    }
505
506    /// Returns the gas accounting information.
507    pub const fn gas(&self) -> &ResultGas {
508        match self {
509            Self::Success { gas, .. } | Self::Revert { gas, .. } | Self::Halt { gas, .. } => gas,
510        }
511    }
512
513    /// Returns the gas used needed for the transaction receipt.
514    pub const fn tx_gas_used(&self) -> u64 {
515        self.gas().tx_gas_used()
516    }
517
518    /// Returns the gas used.
519    #[inline]
520    #[deprecated(
521        since = "32.0.0",
522        note = "Use `tx_gas_used()` instead, `gas_used` is ambiguous after EIP-8037 state gas split"
523    )]
524    pub const fn gas_used(&self) -> u64 {
525        self.tx_gas_used()
526    }
527}
528
529impl<HaltReasonTy: fmt::Display> fmt::Display for ExecutionResult<HaltReasonTy> {
530    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
531        match self {
532            Self::Success {
533                reason,
534                gas,
535                logs,
536                output,
537            } => {
538                write!(f, "Success ({reason}): {gas}")?;
539                if !logs.is_empty() {
540                    write!(
541                        f,
542                        ", {} log{}",
543                        logs.len(),
544                        if logs.len() == 1 { "" } else { "s" }
545                    )?;
546                }
547                write!(f, ", {output}")
548            }
549            Self::Revert { gas, logs, output } => {
550                write!(f, "Revert: {gas}")?;
551                if !logs.is_empty() {
552                    write!(
553                        f,
554                        ", {} log{}",
555                        logs.len(),
556                        if logs.len() == 1 { "" } else { "s" }
557                    )?;
558                }
559                if !output.is_empty() {
560                    write!(f, ", {} bytes output", output.len())?;
561                }
562                Ok(())
563            }
564            Self::Halt { reason, gas, logs } => {
565                write!(f, "Halted: {reason} ({gas})")?;
566                if !logs.is_empty() {
567                    write!(
568                        f,
569                        ", {} log{}",
570                        logs.len(),
571                        if logs.len() == 1 { "" } else { "s" }
572                    )?;
573                }
574                Ok(())
575            }
576        }
577    }
578}
579
580/// Output of a transaction execution
581#[derive(Debug, Clone, PartialEq, Eq, Hash)]
582#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
583pub enum Output {
584    /// Output of a call.
585    Call(Bytes),
586    /// Output of a create.
587    Create(Bytes, Option<Address>),
588}
589
590impl Output {
591    /// Returns the output data of the execution output.
592    pub fn into_data(self) -> Bytes {
593        match self {
594            Output::Call(data) => data,
595            Output::Create(data, _) => data,
596        }
597    }
598
599    /// Returns the output data of the execution output.
600    pub const fn data(&self) -> &Bytes {
601        match self {
602            Output::Call(data) => data,
603            Output::Create(data, _) => data,
604        }
605    }
606
607    /// Returns the created address, if any.
608    pub const fn address(&self) -> Option<&Address> {
609        match self {
610            Output::Call(_) => None,
611            Output::Create(_, address) => address.as_ref(),
612        }
613    }
614}
615
616impl fmt::Display for Output {
617    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618        match self {
619            Output::Call(data) => {
620                if data.is_empty() {
621                    write!(f, "no output")
622                } else {
623                    write!(f, "{} bytes output", data.len())
624                }
625            }
626            Output::Create(data, Some(addr)) => {
627                if data.is_empty() {
628                    write!(f, "contract created at {}", addr)
629                } else {
630                    write!(f, "contract created at {} ({} bytes)", addr, data.len())
631                }
632            }
633            Output::Create(data, None) => {
634                if data.is_empty() {
635                    write!(f, "contract creation (no address)")
636                } else {
637                    write!(f, "contract creation (no address, {} bytes)", data.len())
638                }
639            }
640        }
641    }
642}
643
644/// Type-erased error type.
645#[derive(Debug, Clone)]
646pub struct AnyError(Arc<dyn core::error::Error + Send + Sync>);
647impl AnyError {
648    /// Creates a new [`AnyError`] from any error type.
649    pub fn new(err: impl core::error::Error + Send + Sync + 'static) -> Self {
650        Self(Arc::new(err))
651    }
652}
653
654impl PartialEq for AnyError {
655    fn eq(&self, other: &Self) -> bool {
656        Arc::ptr_eq(&self.0, &other.0)
657    }
658}
659impl Eq for AnyError {}
660impl core::hash::Hash for AnyError {
661    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
662        (Arc::as_ptr(&self.0) as *const ()).hash(state);
663    }
664}
665impl fmt::Display for AnyError {
666    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
667        fmt::Display::fmt(&self.0, f)
668    }
669}
670impl core::error::Error for AnyError {
671    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
672        self.0.source()
673    }
674}
675
676#[cfg(feature = "serde")]
677impl serde::Serialize for AnyError {
678    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
679        serializer.collect_str(self)
680    }
681}
682
683#[derive(Debug)]
684struct StringError(String);
685impl fmt::Display for StringError {
686    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687        f.write_str(&self.0)
688    }
689}
690impl core::error::Error for StringError {}
691
692impl From<String> for AnyError {
693    fn from(value: String) -> Self {
694        Self::new(StringError(value))
695    }
696}
697impl From<&'static str> for AnyError {
698    fn from(s: &'static str) -> Self {
699        Self::new(StringError(s.into()))
700    }
701}
702
703#[cfg(feature = "serde")]
704impl<'de> serde::Deserialize<'de> for AnyError {
705    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
706        let s = String::deserialize(deserializer)?;
707        Ok(s.into())
708    }
709}
710
711/// Main EVM error
712#[derive(Debug, Clone, PartialEq, Eq)]
713#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
714pub enum EVMError<DBError, TransactionError = InvalidTransaction> {
715    /// Transaction validation error
716    Transaction(TransactionError),
717    /// Header validation error
718    Header(InvalidHeader),
719    /// Database error
720    Database(DBError),
721    /// Custom error for non-standard EVM failures.
722    ///
723    /// This includes fatal precompile errors (`PrecompileError::Fatal` and `PrecompileError::FatalAny`)
724    /// errors as well as any custom errors returned by handler registers.
725    Custom(String),
726    /// Custom error for non-standard EVM failures.
727    ///
728    /// This includes fatal precompile errors (`PrecompileError::Fatal` and `PrecompileError::FatalAny`)
729    /// errors as well as any custom errors returned by handler registers.
730    CustomAny(AnyError),
731}
732
733impl<DBError, TransactionValidationErrorT> From<ContextError<DBError>>
734    for EVMError<DBError, TransactionValidationErrorT>
735{
736    fn from(value: ContextError<DBError>) -> Self {
737        match value {
738            ContextError::Db(e) => Self::Database(e),
739            ContextError::Custom(e) => Self::Custom(e),
740        }
741    }
742}
743
744impl<DBError: DBErrorMarker, TX> From<DBError> for EVMError<DBError, TX> {
745    fn from(value: DBError) -> Self {
746        Self::Database(value)
747    }
748}
749
750/// Trait for converting a string to an [`EVMError::Custom`] error.
751pub trait FromStringError {
752    /// Converts a string to an [`EVMError::Custom`] error.
753    fn from_string(value: String) -> Self;
754}
755
756impl<DB, TX> FromStringError for EVMError<DB, TX> {
757    fn from_string(value: String) -> Self {
758        Self::Custom(value)
759    }
760}
761
762impl<DB, TXE: From<InvalidTransaction>> From<InvalidTransaction> for EVMError<DB, TXE> {
763    fn from(value: InvalidTransaction) -> Self {
764        Self::Transaction(TXE::from(value))
765    }
766}
767
768impl<DBError, TransactionValidationErrorT> EVMError<DBError, TransactionValidationErrorT> {
769    /// Maps a `DBError` to a new error type using the provided closure, leaving other variants unchanged.
770    pub fn map_db_err<F, E>(self, op: F) -> EVMError<E, TransactionValidationErrorT>
771    where
772        F: FnOnce(DBError) -> E,
773    {
774        match self {
775            Self::Transaction(e) => EVMError::Transaction(e),
776            Self::Header(e) => EVMError::Header(e),
777            Self::Database(e) => EVMError::Database(op(e)),
778            Self::Custom(e) => EVMError::Custom(e),
779            Self::CustomAny(e) => EVMError::CustomAny(e),
780        }
781    }
782}
783
784impl<DBError, TransactionValidationErrorT> core::error::Error
785    for EVMError<DBError, TransactionValidationErrorT>
786where
787    DBError: core::error::Error + 'static,
788    TransactionValidationErrorT: core::error::Error + 'static,
789{
790    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
791        match self {
792            Self::Transaction(e) => Some(e),
793            Self::Header(e) => Some(e),
794            Self::Database(e) => Some(e),
795            Self::Custom(_) => None,
796            Self::CustomAny(e) => Some(e.0.as_ref()),
797        }
798    }
799}
800
801impl<DBError, TransactionValidationErrorT> fmt::Display
802    for EVMError<DBError, TransactionValidationErrorT>
803where
804    DBError: fmt::Display,
805    TransactionValidationErrorT: fmt::Display,
806{
807    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
808        match self {
809            Self::Transaction(e) => write!(f, "transaction validation error: {e}"),
810            Self::Header(e) => write!(f, "header validation error: {e}"),
811            Self::Database(e) => write!(f, "database error: {e}"),
812            Self::Custom(e) => f.write_str(e),
813            Self::CustomAny(e) => write!(f, "{e}"),
814        }
815    }
816}
817
818impl<DBError, TransactionValidationErrorT> From<InvalidHeader>
819    for EVMError<DBError, TransactionValidationErrorT>
820{
821    fn from(value: InvalidHeader) -> Self {
822        Self::Header(value)
823    }
824}
825
826/// Transaction validation error.
827#[derive(Debug, Clone, PartialEq, Eq, Hash)]
828#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
829pub enum InvalidTransaction {
830    /// When using the EIP-1559 fee model introduced in the London upgrade, transactions specify two primary fee fields:
831    /// - `gas_max_fee`: The maximum total fee a user is willing to pay, inclusive of both base fee and priority fee.
832    /// - `gas_priority_fee`: The extra amount a user is willing to give directly to the miner, often referred to as the "tip".
833    ///
834    /// Provided `gas_priority_fee` exceeds the total `gas_max_fee`.
835    PriorityFeeGreaterThanMaxFee,
836    /// EIP-1559: `gas_price` is less than `basefee`.
837    GasPriceLessThanBasefee,
838    /// `gas_limit` in the tx is bigger than `block_gas_limit`.
839    CallerGasLimitMoreThanBlock,
840    /// Initial gas for a Call is bigger than `gas_limit`.
841    ///
842    /// Initial gas for a Call contains:
843    /// - initial stipend gas
844    /// - gas for access list and input data
845    CallGasCostMoreThanGasLimit {
846        /// Initial gas for a Call.
847        initial_gas: u64,
848        /// Gas limit for the transaction.
849        gas_limit: u64,
850    },
851    /// Gas floor calculated from EIP-7623 Increase calldata cost
852    /// is more than the gas limit.
853    ///
854    /// Tx data is too large to be executed.
855    GasFloorMoreThanGasLimit {
856        /// Gas floor calculated from EIP-7623 Increase calldata cost.
857        gas_floor: u64,
858        /// Gas limit for the transaction.
859        gas_limit: u64,
860    },
861    /// EIP-3607 Reject transactions from senders with deployed code
862    RejectCallerWithCode,
863    /// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
864    LackOfFundForMaxFee {
865        /// Fee for the transaction.
866        fee: Box<U256>,
867        /// Balance of the sender.
868        balance: Box<U256>,
869    },
870    /// Overflow payment in transaction.
871    OverflowPaymentInTransaction,
872    /// Nonce overflows in transaction.
873    NonceOverflowInTransaction,
874    /// Nonce is too high.
875    NonceTooHigh {
876        /// Nonce of the transaction.
877        tx: u64,
878        /// Nonce of the state.
879        state: u64,
880    },
881    /// Nonce is too low.
882    NonceTooLow {
883        /// Nonce of the transaction.
884        tx: u64,
885        /// Nonce of the state.
886        state: u64,
887    },
888    /// EIP-3860: Limit and meter initcode
889    CreateInitCodeSizeLimit,
890    /// Transaction chain id does not match the config chain id.
891    InvalidChainId,
892    /// Missing chain id.
893    MissingChainId,
894    /// Transaction gas limit is greater than the cap.
895    TxGasLimitGreaterThanCap {
896        /// Transaction gas limit.
897        gas_limit: u64,
898        /// Gas limit cap.
899        cap: u64,
900    },
901    /// Access list is not supported for blocks before the Berlin hardfork.
902    AccessListNotSupported,
903    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
904    MaxFeePerBlobGasNotSupported,
905    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks before the Cancun hardfork.
906    BlobVersionedHashesNotSupported,
907    /// Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas` after Cancun.
908    BlobGasPriceGreaterThanMax {
909        /// Block `blob_gas_price`.
910        block_blob_gas_price: u128,
911        /// Tx-specified `max_fee_per_blob_gas`.
912        tx_max_fee_per_blob_gas: u128,
913    },
914    /// There should be at least one blob in Blob transaction.
915    EmptyBlobs,
916    /// Blob transaction can't be a create transaction.
917    ///
918    /// `to` must be present
919    BlobCreateTransaction,
920    /// Transaction has more then `max` blobs
921    TooManyBlobs {
922        /// Maximum number of blobs allowed.
923        max: usize,
924        /// Number of blobs in the transaction.
925        have: usize,
926    },
927    /// Blob transaction contains a versioned hash with an incorrect version
928    BlobVersionNotSupported,
929    /// EIP-7702 is not enabled.
930    AuthorizationListNotSupported,
931    /// EIP-7702 transaction has invalid fields set.
932    AuthorizationListInvalidFields,
933    /// Empty Authorization List is not allowed.
934    EmptyAuthorizationList,
935    /// EIP-2930 is not supported.
936    Eip2930NotSupported,
937    /// EIP-1559 is not supported.
938    Eip1559NotSupported,
939    /// EIP-4844 is not supported.
940    Eip4844NotSupported,
941    /// EIP-7702 is not supported.
942    Eip7702NotSupported,
943    /// EIP-7873 is not supported.
944    Eip7873NotSupported,
945    /// EIP-7873 initcode transaction should have `to` address.
946    Eip7873MissingTarget,
947    /// Custom string error for flexible error handling.
948    Str(Cow<'static, str>),
949}
950
951impl TransactionError for InvalidTransaction {}
952
953impl core::error::Error for InvalidTransaction {}
954
955impl fmt::Display for InvalidTransaction {
956    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
957        match self {
958            Self::PriorityFeeGreaterThanMaxFee => {
959                write!(f, "priority fee is greater than max fee")
960            }
961            Self::GasPriceLessThanBasefee => {
962                write!(f, "gas price is less than basefee")
963            }
964            Self::CallerGasLimitMoreThanBlock => {
965                write!(f, "caller gas limit exceeds the block gas limit")
966            }
967            Self::TxGasLimitGreaterThanCap { gas_limit, cap } => {
968                write!(
969                    f,
970                    "transaction gas limit ({gas_limit}) is greater than the cap ({cap})"
971                )
972            }
973            Self::CallGasCostMoreThanGasLimit {
974                initial_gas,
975                gas_limit,
976            } => {
977                write!(
978                    f,
979                    "call gas cost ({initial_gas}) exceeds the gas limit ({gas_limit})"
980                )
981            }
982            Self::GasFloorMoreThanGasLimit {
983                gas_floor,
984                gas_limit,
985            } => {
986                write!(
987                    f,
988                    "gas floor ({gas_floor}) exceeds the gas limit ({gas_limit})"
989                )
990            }
991            Self::RejectCallerWithCode => {
992                write!(f, "reject transactions from senders with deployed code")
993            }
994            Self::LackOfFundForMaxFee { fee, balance } => {
995                write!(f, "lack of funds ({balance}) for max fee ({fee})")
996            }
997            Self::OverflowPaymentInTransaction => {
998                write!(f, "overflow payment in transaction")
999            }
1000            Self::NonceOverflowInTransaction => {
1001                write!(f, "nonce overflow in transaction")
1002            }
1003            Self::NonceTooHigh { tx, state } => {
1004                write!(f, "nonce {tx} too high, expected {state}")
1005            }
1006            Self::NonceTooLow { tx, state } => {
1007                write!(f, "nonce {tx} too low, expected {state}")
1008            }
1009            Self::CreateInitCodeSizeLimit => {
1010                write!(f, "create initcode size limit")
1011            }
1012            Self::InvalidChainId => write!(f, "invalid chain ID"),
1013            Self::MissingChainId => write!(f, "missing chain ID"),
1014            Self::AccessListNotSupported => write!(f, "access list not supported"),
1015            Self::MaxFeePerBlobGasNotSupported => {
1016                write!(f, "max fee per blob gas not supported")
1017            }
1018            Self::BlobVersionedHashesNotSupported => {
1019                write!(f, "blob versioned hashes not supported")
1020            }
1021            Self::BlobGasPriceGreaterThanMax {
1022                block_blob_gas_price,
1023                tx_max_fee_per_blob_gas,
1024            } => {
1025                write!(
1026                    f,
1027                    "blob gas price ({block_blob_gas_price}) is greater than max fee per blob gas ({tx_max_fee_per_blob_gas})"
1028                )
1029            }
1030            Self::EmptyBlobs => write!(f, "empty blobs"),
1031            Self::BlobCreateTransaction => write!(f, "blob create transaction"),
1032            Self::TooManyBlobs { max, have } => {
1033                write!(f, "too many blobs, have {have}, max {max}")
1034            }
1035            Self::BlobVersionNotSupported => write!(f, "blob version not supported"),
1036            Self::AuthorizationListNotSupported => write!(f, "authorization list not supported"),
1037            Self::AuthorizationListInvalidFields => {
1038                write!(f, "authorization list tx has invalid fields")
1039            }
1040            Self::EmptyAuthorizationList => write!(f, "empty authorization list"),
1041            Self::Eip2930NotSupported => write!(f, "Eip2930 is not supported"),
1042            Self::Eip1559NotSupported => write!(f, "Eip1559 is not supported"),
1043            Self::Eip4844NotSupported => write!(f, "Eip4844 is not supported"),
1044            Self::Eip7702NotSupported => write!(f, "Eip7702 is not supported"),
1045            Self::Eip7873NotSupported => write!(f, "Eip7873 is not supported"),
1046            Self::Eip7873MissingTarget => {
1047                write!(f, "Eip7873 initcode transaction should have `to` address")
1048            }
1049            Self::Str(msg) => f.write_str(msg),
1050        }
1051    }
1052}
1053
1054/// Errors related to misconfiguration of a [`crate::Block`].
1055#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1056#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1057pub enum InvalidHeader {
1058    /// `prevrandao` is not set for Merge and above.
1059    PrevrandaoNotSet,
1060    /// `excess_blob_gas` is not set for Cancun and above.
1061    ExcessBlobGasNotSet,
1062}
1063
1064impl core::error::Error for InvalidHeader {}
1065
1066impl fmt::Display for InvalidHeader {
1067    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1068        match self {
1069            Self::PrevrandaoNotSet => write!(f, "`prevrandao` not set"),
1070            Self::ExcessBlobGasNotSet => write!(f, "`excess_blob_gas` not set"),
1071        }
1072    }
1073}
1074
1075/// Reason a transaction successfully completed.
1076#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1077#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1078pub enum SuccessReason {
1079    /// Stop [`state::bytecode::opcode::STOP`] opcode.
1080    Stop,
1081    /// Return [`state::bytecode::opcode::RETURN`] opcode.
1082    Return,
1083    /// Self destruct opcode.
1084    SelfDestruct,
1085}
1086
1087impl fmt::Display for SuccessReason {
1088    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1089        match self {
1090            Self::Stop => write!(f, "Stop"),
1091            Self::Return => write!(f, "Return"),
1092            Self::SelfDestruct => write!(f, "SelfDestruct"),
1093        }
1094    }
1095}
1096
1097/// Indicates that the EVM has experienced an exceptional halt.
1098///
1099/// This causes execution to immediately end with all gas being consumed.
1100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1102pub enum HaltReason {
1103    /// Out of gas error.
1104    OutOfGas(OutOfGasError),
1105    /// Opcode not found error.
1106    OpcodeNotFound,
1107    /// Invalid FE opcode error.
1108    InvalidFEOpcode,
1109    /// Invalid jump destination.
1110    InvalidJump,
1111    /// The feature or opcode is not activated in hardfork.
1112    NotActivated,
1113    /// Attempting to pop a value from an empty stack.
1114    StackUnderflow,
1115    /// Attempting to push a value onto a full stack.
1116    StackOverflow,
1117    /// Invalid memory or storage offset for [`state::bytecode::opcode::RETURNDATACOPY`].
1118    OutOfOffset,
1119    /// Address collision during contract creation.
1120    CreateCollision,
1121    /// Precompile error.
1122    PrecompileError,
1123    /// Precompile error with message from context.
1124    PrecompileErrorWithContext(String),
1125    /// Nonce overflow.
1126    NonceOverflow,
1127    /// Create init code size exceeds limit (runtime).
1128    CreateContractSizeLimit,
1129    /// Error on created contract that begins with EF
1130    CreateContractStartingWithEF,
1131    /// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
1132    CreateInitCodeSizeLimit,
1133
1134    /* Internal Halts that can be only found inside Inspector */
1135    /// Overflow payment. Not possible to happen on mainnet.
1136    OverflowPayment,
1137    /// State change during static call.
1138    StateChangeDuringStaticCall,
1139    /// Call not allowed inside static call.
1140    CallNotAllowedInsideStatic,
1141    /// Out of funds to pay for the call.
1142    OutOfFunds,
1143    /// Call is too deep.
1144    CallTooDeep,
1145}
1146
1147impl core::error::Error for HaltReason {}
1148
1149impl fmt::Display for HaltReason {
1150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1151        match self {
1152            Self::OutOfGas(err) => write!(f, "{err}"),
1153            Self::OpcodeNotFound => write!(f, "opcode not found"),
1154            Self::InvalidFEOpcode => write!(f, "invalid 0xFE opcode"),
1155            Self::InvalidJump => write!(f, "invalid jump destination"),
1156            Self::NotActivated => write!(f, "feature or opcode not activated"),
1157            Self::StackUnderflow => write!(f, "stack underflow"),
1158            Self::StackOverflow => write!(f, "stack overflow"),
1159            Self::OutOfOffset => write!(f, "out of offset"),
1160            Self::CreateCollision => write!(f, "create collision"),
1161            Self::PrecompileError => write!(f, "precompile error"),
1162            Self::PrecompileErrorWithContext(msg) => write!(f, "precompile error: {msg}"),
1163            Self::NonceOverflow => write!(f, "nonce overflow"),
1164            Self::CreateContractSizeLimit => write!(f, "create contract size limit"),
1165            Self::CreateContractStartingWithEF => {
1166                write!(f, "create contract starting with 0xEF")
1167            }
1168            Self::CreateInitCodeSizeLimit => write!(f, "create initcode size limit"),
1169            Self::OverflowPayment => write!(f, "overflow payment"),
1170            Self::StateChangeDuringStaticCall => write!(f, "state change during static call"),
1171            Self::CallNotAllowedInsideStatic => write!(f, "call not allowed inside static call"),
1172            Self::OutOfFunds => write!(f, "out of funds"),
1173            Self::CallTooDeep => write!(f, "call too deep"),
1174        }
1175    }
1176}
1177
1178/// Out of gas errors.
1179#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1181pub enum OutOfGasError {
1182    /// Basic OOG error. Not enough gas to execute the opcode.
1183    Basic,
1184    /// Tried to expand past memory limit.
1185    MemoryLimit,
1186    /// Basic OOG error from memory expansion
1187    Memory,
1188    /// Precompile threw OOG error
1189    Precompile,
1190    /// When performing something that takes a U256 and casts down to a u64, if its too large this would fire
1191    /// i.e. in `as_usize_or_fail`
1192    InvalidOperand,
1193    /// When performing SSTORE the gasleft is less than or equal to 2300
1194    ReentrancySentry,
1195}
1196
1197impl core::error::Error for OutOfGasError {}
1198
1199impl fmt::Display for OutOfGasError {
1200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1201        match self {
1202            Self::Basic => write!(f, "out of gas"),
1203            Self::MemoryLimit => write!(f, "out of gas: memory limit exceeded"),
1204            Self::Memory => write!(f, "out of gas: memory expansion"),
1205            Self::Precompile => write!(f, "out of gas: precompile"),
1206            Self::InvalidOperand => write!(f, "out of gas: invalid operand"),
1207            Self::ReentrancySentry => write!(f, "out of gas: reentrancy sentry"),
1208        }
1209    }
1210}
1211
1212/// Error that includes transaction index for batch transaction processing.
1213#[derive(Debug, Clone, PartialEq, Eq)]
1214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1215pub struct TransactionIndexedError<Error> {
1216    /// The original error that occurred.
1217    pub error: Error,
1218    /// The index of the transaction that failed.
1219    pub transaction_index: usize,
1220}
1221
1222impl<Error> TransactionIndexedError<Error> {
1223    /// Create a new `TransactionIndexedError` with the given error and transaction index.
1224    #[must_use]
1225    pub const fn new(error: Error, transaction_index: usize) -> Self {
1226        Self {
1227            error,
1228            transaction_index,
1229        }
1230    }
1231
1232    /// Get a reference to the underlying error.
1233    pub const fn error(&self) -> &Error {
1234        &self.error
1235    }
1236
1237    /// Convert into the underlying error.
1238    #[must_use]
1239    pub fn into_error(self) -> Error {
1240        self.error
1241    }
1242}
1243
1244impl<Error: fmt::Display> fmt::Display for TransactionIndexedError<Error> {
1245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1246        write!(
1247            f,
1248            "transaction {} failed: {}",
1249            self.transaction_index, self.error
1250        )
1251    }
1252}
1253
1254impl<Error: core::error::Error + 'static> core::error::Error for TransactionIndexedError<Error> {
1255    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
1256        Some(&self.error)
1257    }
1258}
1259
1260impl From<&'static str> for InvalidTransaction {
1261    fn from(s: &'static str) -> Self {
1262        Self::Str(Cow::Borrowed(s))
1263    }
1264}
1265
1266impl From<String> for InvalidTransaction {
1267    fn from(s: String) -> Self {
1268        Self::Str(Cow::Owned(s))
1269    }
1270}
1271
1272#[cfg(test)]
1273mod tests {
1274    use super::*;
1275
1276    #[test]
1277    fn test_execution_result_display() {
1278        let result: ExecutionResult<HaltReason> = ExecutionResult::Success {
1279            reason: SuccessReason::Return,
1280            gas: ResultGas::default()
1281                .with_total_gas_spent(100000)
1282                .with_refunded(26000)
1283                .with_floor_gas(5000),
1284            logs: vec![Log::default(), Log::default()],
1285            output: Output::Call(Bytes::from(vec![1, 2, 3])),
1286        };
1287        assert_eq!(
1288            result.to_string(),
1289            "Success (Return): Gas used: 74000, total spent: 100000, refunded: 26000, floor: 5000, 2 logs, 3 bytes output"
1290        );
1291
1292        let result: ExecutionResult<HaltReason> = ExecutionResult::Revert {
1293            gas: ResultGas::default()
1294                .with_total_gas_spent(100000)
1295                .with_refunded(100000),
1296            logs: vec![],
1297            output: Bytes::from(vec![1, 2, 3, 4]),
1298        };
1299        assert_eq!(
1300            result.to_string(),
1301            "Revert: Gas used: 0, total spent: 100000, refunded: 100000, 4 bytes output"
1302        );
1303
1304        let result: ExecutionResult<HaltReason> = ExecutionResult::Halt {
1305            reason: HaltReason::OutOfGas(OutOfGasError::Basic),
1306            gas: ResultGas::default()
1307                .with_total_gas_spent(1000000)
1308                .with_refunded(1000000),
1309            logs: vec![],
1310        };
1311        assert_eq!(
1312            result.to_string(),
1313            "Halted: out of gas (Gas used: 0, total spent: 1000000, refunded: 1000000)"
1314        );
1315    }
1316
1317    #[test]
1318    fn test_result_gas_display() {
1319        // No refund, no floor
1320        assert_eq!(
1321            ResultGas::default().with_total_gas_spent(21000).to_string(),
1322            "Gas used: 21000, total spent: 21000"
1323        );
1324        // With refund
1325        assert_eq!(
1326            ResultGas::default()
1327                .with_total_gas_spent(50000)
1328                .with_refunded(10000)
1329                .to_string(),
1330            "Gas used: 40000, total spent: 50000, refunded: 10000"
1331        );
1332        // With refund and floor
1333        assert_eq!(
1334            ResultGas::default()
1335                .with_total_gas_spent(50000)
1336                .with_refunded(10000)
1337                .with_floor_gas(30000)
1338                .to_string(),
1339            "Gas used: 40000, total spent: 50000, refunded: 10000, floor: 30000"
1340        );
1341    }
1342
1343    #[test]
1344    fn test_result_gas_used_and_remaining() {
1345        let gas = ResultGas::default()
1346            .with_total_gas_spent(100)
1347            .with_refunded(30);
1348        assert_eq!(gas.total_gas_spent(), 100);
1349        assert_eq!(gas.inner_refunded(), 30);
1350        assert_eq!(gas.spent_sub_refunded(), 70);
1351
1352        // Saturating: refunded > spent
1353        let gas = ResultGas::default()
1354            .with_total_gas_spent(10)
1355            .with_refunded(50);
1356        assert_eq!(gas.spent_sub_refunded(), 0);
1357    }
1358
1359    #[test]
1360    fn test_final_refunded_with_floor_gas() {
1361        // No floor gas: final_refunded == refunded
1362        let gas = ResultGas::default()
1363            .with_total_gas_spent(50000)
1364            .with_refunded(10000);
1365        assert_eq!(gas.tx_gas_used(), 40000);
1366        assert_eq!(gas.final_refunded(), 10000);
1367
1368        // Floor gas active (spent_sub_refunded < floor_gas): final_refunded == 0
1369        // spent=50000, refunded=10000, spent_sub_refunded=40000 < floor_gas=45000
1370        let gas = ResultGas::default()
1371            .with_total_gas_spent(50000)
1372            .with_refunded(10000)
1373            .with_floor_gas(45000);
1374        assert_eq!(gas.tx_gas_used(), 45000);
1375        assert_eq!(gas.final_refunded(), 0);
1376
1377        // Floor gas inactive (spent_sub_refunded >= floor_gas): final_refunded == refunded
1378        // spent=50000, refunded=10000, spent_sub_refunded=40000 >= floor_gas=30000
1379        let gas = ResultGas::default()
1380            .with_total_gas_spent(50000)
1381            .with_refunded(10000)
1382            .with_floor_gas(30000);
1383        assert_eq!(gas.tx_gas_used(), 40000);
1384        assert_eq!(gas.final_refunded(), 10000);
1385
1386        // Edge case: spent_sub_refunded == floor_gas exactly
1387        // spent=50000, refunded=10000, spent_sub_refunded=40000 == floor_gas=40000
1388        let gas = ResultGas::default()
1389            .with_total_gas_spent(50000)
1390            .with_refunded(10000)
1391            .with_floor_gas(40000);
1392        assert_eq!(gas.tx_gas_used(), 40000);
1393        assert_eq!(gas.final_refunded(), 10000);
1394    }
1395
1396    #[test]
1397    fn test_block_regular_gas_used_no_floor_no_refund() {
1398        // block_regular_gas_used = total_gas_spent - state_gas_spent.
1399        // Refund and floor only affect tx_gas_used, never block_regular.
1400
1401        // No state, no refund, no floor.
1402        let gas = ResultGas::default().with_total_gas_spent(100_000);
1403        assert_eq!(gas.block_regular_gas_used(), 100_000);
1404
1405        // With state gas.
1406        let gas = ResultGas::default()
1407            .with_total_gas_spent(100_000)
1408            .with_state_gas_spent(30_000);
1409        assert_eq!(gas.block_regular_gas_used(), 70_000);
1410        assert_eq!(gas.block_state_gas_used(), 30_000);
1411
1412        // Refund present: tx_gas_used drops, block_regular does not.
1413        let gas = ResultGas::default()
1414            .with_total_gas_spent(100_000)
1415            .with_refunded(10_000)
1416            .with_state_gas_spent(30_000);
1417        assert_eq!(gas.tx_gas_used(), 90_000);
1418        assert_eq!(gas.block_regular_gas_used(), 70_000);
1419
1420        // Floor active: tx_gas_used floors up, block_regular does not.
1421        let gas = ResultGas::default()
1422            .with_total_gas_spent(100_000)
1423            .with_refunded(90_000)
1424            .with_floor_gas(50_000)
1425            .with_state_gas_spent(30_000);
1426        assert_eq!(gas.tx_gas_used(), 50_000);
1427        assert_eq!(gas.block_regular_gas_used(), 70_000);
1428
1429        // State gas exceeds total → saturates to 0.
1430        let gas = ResultGas::default()
1431            .with_total_gas_spent(20_000)
1432            .with_state_gas_spent(30_000);
1433        assert_eq!(gas.block_regular_gas_used(), 0);
1434    }
1435}