Skip to main content

ethrex_levm/
errors.rs

1use bytes::Bytes;
2use derive_more::derive::Display;
3use ethrex_common::{
4    Address, H256, U256,
5    types::{FakeExponentialError, Log},
6};
7use serde::{Deserialize, Serialize};
8use thiserror;
9
10#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize, Display)]
11pub enum VMError {
12    /// Errors that break execution, they shouldn't ever happen. Contains subcategory `DatabaseError`.
13    Internal(#[from] InternalError),
14    /// Returned when a transaction doesn't pass all validations before executing.
15    TxValidation(#[from] TxValidationError),
16    /// Errors contemplated by the EVM, they revert and consume all gas of the current context.
17    ExceptionalHalt(#[from] ExceptionalHalt),
18    /// Revert Opcode called. It behaves like ExceptionalHalt, except it doesn't consume all gas left.
19    RevertOpcode,
20}
21
22impl VMError {
23    /// These errors are unexpected and indicate critical issues.
24    /// They should not cause a transaction to revert silently but instead fail loudly, propagating the error.
25    pub fn should_propagate(&self) -> bool {
26        matches!(self, VMError::Internal(_))
27    }
28
29    /// Error triggered by revert opcode. This error doesn't consume all gas left in context.
30    pub fn is_revert_opcode(&self) -> bool {
31        matches!(self, VMError::RevertOpcode)
32    }
33}
34
35impl From<DatabaseError> for VMError {
36    fn from(err: DatabaseError) -> Self {
37        VMError::Internal(InternalError::Database(err))
38    }
39}
40
41impl From<PrecompileError> for VMError {
42    fn from(err: PrecompileError) -> Self {
43        VMError::ExceptionalHalt(ExceptionalHalt::Precompile(err))
44    }
45}
46
47/// Useful to use ? in try_into, specially when slicing with known bounds to fixed size arrays,
48/// which is a error that never really happens.
49impl From<std::array::TryFromSliceError> for VMError {
50    fn from(_: std::array::TryFromSliceError) -> Self {
51        VMError::Internal(InternalError::TypeConversion)
52    }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize)]
56pub enum ExceptionalHalt {
57    #[error("Stack Underflow")]
58    StackUnderflow,
59    #[error("Stack Overflow")]
60    StackOverflow,
61    #[error("Invalid Jump")]
62    InvalidJump,
63    #[error("Opcode Not Allowed In Static Context")]
64    OpcodeNotAllowedInStaticContext,
65    #[error("Invalid Contract Prefix")]
66    InvalidContractPrefix,
67    #[error("Very Large Number")]
68    VeryLargeNumber,
69    #[error("Invalid Opcode")]
70    InvalidOpcode,
71    #[error("Address Already Occupied")]
72    AddressAlreadyOccupied,
73    #[error("Contract Output Too Big")]
74    ContractOutputTooBig,
75    #[error("Offset out of bounds")]
76    OutOfBounds,
77    #[error("Out Of Gas")]
78    OutOfGas,
79    #[error("Precompile execution error: {0}")]
80    Precompile(#[from] PrecompileError),
81}
82
83// Error strings are attached to execution-spec-tests mapping https://github.com/ethereum/execution-spec-tests
84// If any change is made here without changing the mapper it will break some hive tests.
85#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize)]
86pub enum TxValidationError {
87    #[error("Sender account {0} shouldn't be a contract")]
88    SenderNotEOA(Address),
89    #[error("Insufficient account funds")]
90    InsufficientAccountFunds,
91    #[error("Nonce is max")]
92    NonceIsMax,
93    #[error("Nonce mismatch: expected {expected}, got {actual}")]
94    NonceMismatch { expected: u64, actual: u64 },
95    #[error("Initcode size exceeded, max size: {max_size}, actual size: {actual_size}")]
96    InitcodeSizeExceeded { max_size: usize, actual_size: usize },
97    #[error("Priority fee {priority_fee} is greater than max fee per gas {max_fee_per_gas}")]
98    PriorityGreaterThanMaxFeePerGas {
99        priority_fee: U256,
100        max_fee_per_gas: U256,
101    },
102    #[error("Transaction gas limit lower than the minimum gas cost to execute the transaction")]
103    IntrinsicGasTooLow,
104    #[error("Transaction gas limit lower than the gas cost floor for calldata tokens")]
105    IntrinsicGasBelowFloorGasCost,
106    #[error(
107        "Gas allowance exceeded. Block gas limit: {block_gas_limit}, transaction gas limit: {tx_gas_limit}"
108    )]
109    GasAllowanceExceeded {
110        block_gas_limit: u64,
111        tx_gas_limit: u64,
112    },
113    #[error("Insufficient max fee per gas")]
114    InsufficientMaxFeePerGas,
115    #[error(
116        "Insufficient max fee per blob gas. Expected at least {base_fee_per_blob_gas}, got: {tx_max_fee_per_blob_gas}"
117    )]
118    InsufficientMaxFeePerBlobGas {
119        base_fee_per_blob_gas: U256,
120        tx_max_fee_per_blob_gas: U256,
121    },
122    #[error("Type 3 transactions are not supported before the Cancun fork")]
123    Type3TxPreFork,
124    #[error("Type 3 transaction without blobs")]
125    Type3TxZeroBlobs,
126    #[error("Invalid blob versioned hash")]
127    Type3TxInvalidBlobVersionedHash,
128    #[error(
129        "Blob count exceeded. Max blob count: {max_blob_count}, actual blob count: {actual_blob_count}"
130    )]
131    Type3TxBlobCountExceeded {
132        max_blob_count: usize,
133        actual_blob_count: usize,
134    },
135    #[error("Contract creation in blob transaction")]
136    Type3TxContractCreation,
137    #[error("Type 4 transactions are not supported before the Prague fork")]
138    Type4TxPreFork,
139    #[error("Empty authorization list in type 4 transaction")]
140    Type4TxAuthorizationListIsEmpty,
141    #[error("Contract creation in type 4 transaction")]
142    Type4TxContractCreation,
143    #[error("Gas limit price product overflow")]
144    GasLimitPriceProductOverflow,
145    #[error(
146        "Transaction gas limit exceeds maximum. Transaction hash: {tx_hash}, transaction gas limit: {tx_gas_limit}"
147    )]
148    TxMaxGasLimitExceeded { tx_hash: H256, tx_gas_limit: u64 },
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize)]
152pub enum InternalError {
153    #[error("Arithmetic operation overflowed")]
154    Overflow,
155    #[error("Arithmetic operation underflowed")]
156    Underflow,
157    #[error("Cannot divide by zero")]
158    DivisionByZero,
159    #[error("Tried to convert one type to another")]
160    TypeConversion,
161    #[error("CallFrame not found")]
162    CallFrame,
163    #[error("Tried to slice non-existing data")]
164    Slicing,
165    #[error("Account not found when it should've been in the cache.")]
166    AccountNotFound,
167    #[error("Invalid precompile address. Tried to execute a precompile that does not exist.")]
168    InvalidPrecompileAddress,
169    #[error("Invalid Fork")]
170    InvalidFork,
171    #[error("Account should had been delegated")]
172    AccountNotDelegated,
173    #[error("No recipient found for privileged transaction")]
174    RecipientNotFoundForPrivilegedTransaction,
175    #[error("Memory Size Sverflow")]
176    MemorySizeOverflow,
177    #[error("Custom error: {0}")]
178    Custom(String),
179    /// Unexpected error when accessing the database, used in trait `Database`.
180    #[error("Database access error: {0}")]
181    Database(#[from] DatabaseError),
182    #[error("{0}")]
183    FakeExponentialError(#[from] FakeExponentialError),
184}
185
186impl InternalError {
187    pub fn msg(msg: &'static str) -> Self {
188        Self::Custom(msg.to_owned())
189    }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize)]
193pub enum PrecompileError {
194    #[error("Error while parsing the calldata")]
195    ParsingInputError,
196    #[error("There is not enough gas to execute precompiled contract")]
197    NotEnoughGas,
198    #[error("Invalid point")]
199    InvalidPoint,
200    #[error("The point is not in the subgroup")]
201    PointNotInSubgroup,
202    #[error("The G1 point is not in the curve")]
203    BLS12381G1PointNotInCurve,
204    #[error("The G2 point is not in the curve")]
205    BLS12381G2PointNotInCurve,
206    #[error("Mod-exp base length is too large")]
207    ModExpBaseTooLarge,
208    #[error("Mod-exp exponent length is too large")]
209    ModExpExpTooLarge,
210    #[error("Mod-exp modulus length is too large")]
211    ModExpModulusTooLarge,
212    #[error("Coordinate Exceeds Field Modulus")]
213    CoordinateExceedsFieldModulus,
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize)]
217pub enum DatabaseError {
218    #[error("{0}")]
219    Custom(String),
220}
221
222#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
223pub enum OpcodeResult {
224    Continue,
225    Halt,
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
229pub enum TxResult {
230    Success,
231    Revert(VMError),
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235pub struct ExecutionReport {
236    pub result: TxResult,
237    /// Gas used before refunds (for block-level accounting).
238    /// Pre-EIP-7778: This is the post-refund gas.
239    /// Post-EIP-7778: This is the pre-refund gas.
240    pub gas_used: u64,
241    /// Gas spent after refunds (what the user actually pays).
242    /// This is always the post-refund gas value.
243    /// Pre-EIP-7778: Same as gas_used.
244    /// Post-EIP-7778: gas_used - refunds (capped).
245    pub gas_spent: u64,
246    pub gas_refunded: u64,
247    /// EIP-8037: State gas portion of gas_used (Amsterdam+).
248    /// Block gas_used = max(sum(regular_gas), sum(state_gas)).
249    pub state_gas_used: u64,
250    pub output: Bytes,
251    pub logs: Vec<Log>,
252}
253
254impl ExecutionReport {
255    pub fn is_success(&self) -> bool {
256        matches!(self.result, TxResult::Success)
257    }
258}
259
260#[derive(Debug, Clone, PartialEq, Eq)]
261pub struct ContextResult {
262    pub result: TxResult,
263    /// Gas used before refunds (for block-level accounting).
264    pub gas_used: u64,
265    /// Gas spent after refunds (what the user actually pays).
266    pub gas_spent: u64,
267    pub output: Bytes,
268}
269
270impl ContextResult {
271    pub fn is_success(&self) -> bool {
272        matches!(self.result, TxResult::Success)
273    }
274
275    /// Returns true if this result is an address collision error.
276    pub fn is_collision(&self) -> bool {
277        matches!(
278            self.result,
279            TxResult::Revert(VMError::ExceptionalHalt(
280                ExceptionalHalt::AddressAlreadyOccupied
281            ))
282        )
283    }
284
285    /// True if the failure was caused by the REVERT opcode (intentional revert).
286    /// Used to gate behaviour that differs between REVERT and ExceptionalHalt —
287    /// e.g. return data is propagated to the parent on REVERT only.
288    pub fn is_revert_opcode(&self) -> bool {
289        matches!(self.result, TxResult::Revert(VMError::RevertOpcode))
290    }
291}