use crate::{context::ContextError, transaction::TransactionError};
use core::fmt::{self, Debug};
use database_interface::DBErrorMarker;
use primitives::{Address, Bytes, Log, U256};
use state::EvmState;
use std::{borrow::Cow, boxed::Box, string::String, sync::Arc, vec::Vec};
pub trait HaltReasonTr: Clone + Debug + PartialEq + Eq + From<HaltReason> {}
impl<T> HaltReasonTr for T where T: Clone + Debug + PartialEq + Eq + From<HaltReason> {}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExecResultAndState<R, S = EvmState> {
pub result: R,
pub state: S,
}
pub type ResultAndState<H = HaltReason, S = EvmState> = ExecResultAndState<ExecutionResult<H>, S>;
pub type ResultVecAndState<R, S> = ExecResultAndState<Vec<R>, S>;
impl<R, S> ExecResultAndState<R, S> {
pub fn new(result: R, state: S) -> Self {
Self { result, state }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResultGas {
#[cfg_attr(feature = "serde", serde(rename = "gas_spent"))]
total_gas_spent: u64,
#[cfg_attr(feature = "serde", serde(default))]
state_gas_spent: u64,
#[cfg_attr(feature = "serde", serde(rename = "gas_refunded"))]
refunded: u64,
floor_gas: u64,
}
impl ResultGas {
#[inline]
#[deprecated(
since = "32.0.0",
note = "It can be a footgun as gas limit is removed, use ResultGas::with_* functions instead"
)]
pub const fn new(total_gas_spent: u64, refunded: u64, floor_gas: u64) -> Self {
Self {
total_gas_spent,
refunded,
floor_gas,
state_gas_spent: 0,
}
}
#[inline]
pub const fn new_with_state_gas(
total_gas_spent: u64,
refunded: u64,
floor_gas: u64,
state_gas_spent: u64,
) -> Self {
Self {
total_gas_spent,
refunded,
floor_gas,
state_gas_spent,
}
}
#[inline]
pub const fn total_gas_spent(&self) -> u64 {
self.total_gas_spent
}
#[inline]
pub const fn state_gas_spent(&self) -> u64 {
self.state_gas_spent
}
#[inline]
pub const fn floor_gas(&self) -> u64 {
self.floor_gas
}
#[inline]
pub const fn inner_refunded(&self) -> u64 {
self.refunded
}
#[inline]
#[deprecated(
since = "32.0.0",
note = "After EIP-8037 gas is split on
regular and state gas, this method is no longer valid.
Use [`ResultGas::total_gas_spent`] instead"
)]
pub const fn spent(&self) -> u64 {
self.total_gas_spent()
}
#[inline]
pub fn set_total_gas_spent(&mut self, total_gas_spent: u64) {
self.total_gas_spent = total_gas_spent;
}
#[inline]
pub fn set_refunded(&mut self, refunded: u64) {
self.refunded = refunded;
}
#[inline]
pub fn set_floor_gas(&mut self, floor_gas: u64) {
self.floor_gas = floor_gas;
}
#[inline]
pub fn set_state_gas_spent(&mut self, state_gas_spent: u64) {
self.state_gas_spent = state_gas_spent;
}
#[inline]
#[deprecated(
since = "32.0.0",
note = "After EIP-8037 gas is split on
regular and state gas, this method is no longer valid.
Use [`ResultGas::set_total_gas_spent`] instead"
)]
pub fn set_spent(&mut self, spent: u64) {
self.total_gas_spent = spent;
}
#[inline]
pub const fn with_total_gas_spent(mut self, total_gas_spent: u64) -> Self {
self.total_gas_spent = total_gas_spent;
self
}
#[inline]
pub const fn with_refunded(mut self, refunded: u64) -> Self {
self.refunded = refunded;
self
}
#[inline]
pub const fn with_floor_gas(mut self, floor_gas: u64) -> Self {
self.floor_gas = floor_gas;
self
}
#[inline]
pub const fn with_state_gas_spent(mut self, state_gas_spent: u64) -> Self {
self.state_gas_spent = state_gas_spent;
self
}
#[inline]
#[deprecated(
since = "32.0.0",
note = "After EIP-8037 gas is split on
regular and state gas, this method is no longer valid.
Use [`ResultGas::with_total_gas_spent`] instead"
)]
pub const fn with_spent(mut self, spent: u64) -> Self {
self.total_gas_spent = spent;
self
}
#[inline]
pub const fn tx_gas_used(&self) -> u64 {
let total_gas_spent = self.total_gas_spent();
let tx_gas_refunded = total_gas_spent.saturating_sub(self.inner_refunded());
max(tx_gas_refunded, self.floor_gas())
}
#[inline]
pub const fn block_regular_gas_used(&self) -> u64 {
let execution_gas_spent = self
.total_gas_spent()
.saturating_sub(self.state_gas_spent());
max(execution_gas_spent, self.floor_gas())
}
#[inline]
pub const fn block_state_gas_used(&self) -> u64 {
self.state_gas_spent()
}
#[inline]
#[deprecated(
since = "32.0.0",
note = "Used is not descriptive enough, use [`ResultGas::tx_gas_used`] instead"
)]
pub const fn used(&self) -> u64 {
let spent_sub_refunded = self.spent_sub_refunded();
if spent_sub_refunded < self.floor_gas {
return self.floor_gas;
}
spent_sub_refunded
}
#[inline]
pub const fn spent_sub_refunded(&self) -> u64 {
self.total_gas_spent().saturating_sub(self.refunded)
}
#[inline]
pub const fn final_refunded(&self) -> u64 {
if self.spent_sub_refunded() < self.floor_gas {
0
} else {
self.refunded
}
}
}
#[inline(always)]
pub const fn max(a: u64, b: u64) -> u64 {
if a > b {
a
} else {
b
}
}
#[inline(always)]
pub const fn min(a: u64, b: u64) -> u64 {
if a < b {
a
} else {
b
}
}
impl fmt::Display for ResultGas {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Gas used: {}, total spent: {}",
self.tx_gas_used(),
self.total_gas_spent()
)?;
if self.refunded > 0 {
write!(f, ", refunded: {}", self.refunded)?;
}
if self.floor_gas > 0 {
write!(f, ", floor: {}", self.floor_gas)?;
}
if self.state_gas_spent > 0 {
write!(f, ", state_gas: {}", self.state_gas_spent)?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ExecutionResult<HaltReasonTy = HaltReason> {
Success {
reason: SuccessReason,
gas: ResultGas,
logs: Vec<Log>,
output: Output,
},
Revert {
gas: ResultGas,
logs: Vec<Log>,
output: Bytes,
},
Halt {
reason: HaltReasonTy,
gas: ResultGas,
logs: Vec<Log>,
},
}
impl<HaltReasonTy> ExecutionResult<HaltReasonTy> {
pub fn is_success(&self) -> bool {
matches!(self, Self::Success { .. })
}
pub fn map_haltreason<F, OHR>(self, op: F) -> ExecutionResult<OHR>
where
F: FnOnce(HaltReasonTy) -> OHR,
{
match self {
Self::Success {
reason,
gas,
logs,
output,
} => ExecutionResult::Success {
reason,
gas,
logs,
output,
},
Self::Revert { gas, logs, output } => ExecutionResult::Revert { gas, logs, output },
Self::Halt { reason, gas, logs } => ExecutionResult::Halt {
reason: op(reason),
gas,
logs,
},
}
}
pub fn created_address(&self) -> Option<Address> {
match self {
Self::Success { output, .. } => output.address().cloned(),
_ => None,
}
}
pub fn is_halt(&self) -> bool {
matches!(self, Self::Halt { .. })
}
pub fn output(&self) -> Option<&Bytes> {
match self {
Self::Success { output, .. } => Some(output.data()),
Self::Revert { output, .. } => Some(output),
_ => None,
}
}
pub fn into_output(self) -> Option<Bytes> {
match self {
Self::Success { output, .. } => Some(output.into_data()),
Self::Revert { output, .. } => Some(output),
_ => None,
}
}
pub fn logs(&self) -> &[Log] {
match self {
Self::Success { logs, .. } | Self::Revert { logs, .. } | Self::Halt { logs, .. } => {
logs.as_slice()
}
}
}
pub fn into_logs(self) -> Vec<Log> {
match self {
Self::Success { logs, .. } | Self::Revert { logs, .. } | Self::Halt { logs, .. } => {
logs
}
}
}
pub fn gas(&self) -> &ResultGas {
match self {
Self::Success { gas, .. } | Self::Revert { gas, .. } | Self::Halt { gas, .. } => gas,
}
}
pub fn tx_gas_used(&self) -> u64 {
self.gas().tx_gas_used()
}
#[inline]
#[deprecated(
since = "32.0.0",
note = "Use `tx_gas_used()` instead, `gas_used` is ambiguous after EIP-8037 state gas split"
)]
pub fn gas_used(&self) -> u64 {
self.tx_gas_used()
}
}
impl<HaltReasonTy: fmt::Display> fmt::Display for ExecutionResult<HaltReasonTy> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Success {
reason,
gas,
logs,
output,
} => {
write!(f, "Success ({reason}): {gas}")?;
if !logs.is_empty() {
write!(
f,
", {} log{}",
logs.len(),
if logs.len() == 1 { "" } else { "s" }
)?;
}
write!(f, ", {output}")
}
Self::Revert { gas, logs, output } => {
write!(f, "Revert: {gas}")?;
if !logs.is_empty() {
write!(
f,
", {} log{}",
logs.len(),
if logs.len() == 1 { "" } else { "s" }
)?;
}
if !output.is_empty() {
write!(f, ", {} bytes output", output.len())?;
}
Ok(())
}
Self::Halt { reason, gas, logs } => {
write!(f, "Halted: {reason} ({gas})")?;
if !logs.is_empty() {
write!(
f,
", {} log{}",
logs.len(),
if logs.len() == 1 { "" } else { "s" }
)?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Output {
Call(Bytes),
Create(Bytes, Option<Address>),
}
impl Output {
pub fn into_data(self) -> Bytes {
match self {
Output::Call(data) => data,
Output::Create(data, _) => data,
}
}
pub fn data(&self) -> &Bytes {
match self {
Output::Call(data) => data,
Output::Create(data, _) => data,
}
}
pub fn address(&self) -> Option<&Address> {
match self {
Output::Call(_) => None,
Output::Create(_, address) => address.as_ref(),
}
}
}
impl fmt::Display for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Output::Call(data) => {
if data.is_empty() {
write!(f, "no output")
} else {
write!(f, "{} bytes output", data.len())
}
}
Output::Create(data, Some(addr)) => {
if data.is_empty() {
write!(f, "contract created at {}", addr)
} else {
write!(f, "contract created at {} ({} bytes)", addr, data.len())
}
}
Output::Create(data, None) => {
if data.is_empty() {
write!(f, "contract creation (no address)")
} else {
write!(f, "contract creation (no address, {} bytes)", data.len())
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct AnyError(Arc<dyn core::error::Error + Send + Sync>);
impl AnyError {
pub fn new(err: impl core::error::Error + Send + Sync + 'static) -> Self {
Self(Arc::new(err))
}
}
impl PartialEq for AnyError {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for AnyError {}
impl core::hash::Hash for AnyError {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
(Arc::as_ptr(&self.0) as *const ()).hash(state);
}
}
impl fmt::Display for AnyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl core::error::Error for AnyError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.0.source()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for AnyError {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[derive(Debug)]
struct StringError(String);
impl fmt::Display for StringError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl core::error::Error for StringError {}
impl From<String> for AnyError {
fn from(value: String) -> Self {
Self::new(StringError(value))
}
}
impl From<&'static str> for AnyError {
fn from(s: &'static str) -> Self {
Self::new(StringError(s.into()))
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for AnyError {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(s.into())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EVMError<DBError, TransactionError = InvalidTransaction> {
Transaction(TransactionError),
Header(InvalidHeader),
Database(DBError),
Custom(String),
CustomAny(AnyError),
}
impl<DBError, TransactionValidationErrorT> From<ContextError<DBError>>
for EVMError<DBError, TransactionValidationErrorT>
{
fn from(value: ContextError<DBError>) -> Self {
match value {
ContextError::Db(e) => Self::Database(e),
ContextError::Custom(e) => Self::Custom(e),
}
}
}
impl<DBError: DBErrorMarker, TX> From<DBError> for EVMError<DBError, TX> {
fn from(value: DBError) -> Self {
Self::Database(value)
}
}
pub trait FromStringError {
fn from_string(value: String) -> Self;
}
impl<DB, TX> FromStringError for EVMError<DB, TX> {
fn from_string(value: String) -> Self {
Self::Custom(value)
}
}
impl<DB, TXE: From<InvalidTransaction>> From<InvalidTransaction> for EVMError<DB, TXE> {
fn from(value: InvalidTransaction) -> Self {
Self::Transaction(TXE::from(value))
}
}
impl<DBError, TransactionValidationErrorT> EVMError<DBError, TransactionValidationErrorT> {
pub fn map_db_err<F, E>(self, op: F) -> EVMError<E, TransactionValidationErrorT>
where
F: FnOnce(DBError) -> E,
{
match self {
Self::Transaction(e) => EVMError::Transaction(e),
Self::Header(e) => EVMError::Header(e),
Self::Database(e) => EVMError::Database(op(e)),
Self::Custom(e) => EVMError::Custom(e),
Self::CustomAny(e) => EVMError::CustomAny(e),
}
}
}
impl<DBError, TransactionValidationErrorT> core::error::Error
for EVMError<DBError, TransactionValidationErrorT>
where
DBError: core::error::Error + 'static,
TransactionValidationErrorT: core::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Transaction(e) => Some(e),
Self::Header(e) => Some(e),
Self::Database(e) => Some(e),
Self::Custom(_) => None,
Self::CustomAny(e) => Some(e.0.as_ref()),
}
}
}
impl<DBError, TransactionValidationErrorT> fmt::Display
for EVMError<DBError, TransactionValidationErrorT>
where
DBError: fmt::Display,
TransactionValidationErrorT: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Transaction(e) => write!(f, "transaction validation error: {e}"),
Self::Header(e) => write!(f, "header validation error: {e}"),
Self::Database(e) => write!(f, "database error: {e}"),
Self::Custom(e) => f.write_str(e),
Self::CustomAny(e) => write!(f, "{e}"),
}
}
}
impl<DBError, TransactionValidationErrorT> From<InvalidHeader>
for EVMError<DBError, TransactionValidationErrorT>
{
fn from(value: InvalidHeader) -> Self {
Self::Header(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum InvalidTransaction {
PriorityFeeGreaterThanMaxFee,
GasPriceLessThanBasefee,
CallerGasLimitMoreThanBlock,
CallGasCostMoreThanGasLimit {
initial_gas: u64,
gas_limit: u64,
},
GasFloorMoreThanGasLimit {
gas_floor: u64,
gas_limit: u64,
},
RejectCallerWithCode,
LackOfFundForMaxFee {
fee: Box<U256>,
balance: Box<U256>,
},
OverflowPaymentInTransaction,
NonceOverflowInTransaction,
NonceTooHigh {
tx: u64,
state: u64,
},
NonceTooLow {
tx: u64,
state: u64,
},
CreateInitCodeSizeLimit,
InvalidChainId,
MissingChainId,
TxGasLimitGreaterThanCap {
gas_limit: u64,
cap: u64,
},
AccessListNotSupported,
MaxFeePerBlobGasNotSupported,
BlobVersionedHashesNotSupported,
BlobGasPriceGreaterThanMax {
block_blob_gas_price: u128,
tx_max_fee_per_blob_gas: u128,
},
EmptyBlobs,
BlobCreateTransaction,
TooManyBlobs {
max: usize,
have: usize,
},
BlobVersionNotSupported,
AuthorizationListNotSupported,
AuthorizationListInvalidFields,
EmptyAuthorizationList,
Eip2930NotSupported,
Eip1559NotSupported,
Eip4844NotSupported,
Eip7702NotSupported,
Eip7873NotSupported,
Eip7873MissingTarget,
Str(Cow<'static, str>),
}
impl TransactionError for InvalidTransaction {}
impl core::error::Error for InvalidTransaction {}
impl fmt::Display for InvalidTransaction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PriorityFeeGreaterThanMaxFee => {
write!(f, "priority fee is greater than max fee")
}
Self::GasPriceLessThanBasefee => {
write!(f, "gas price is less than basefee")
}
Self::CallerGasLimitMoreThanBlock => {
write!(f, "caller gas limit exceeds the block gas limit")
}
Self::TxGasLimitGreaterThanCap { gas_limit, cap } => {
write!(
f,
"transaction gas limit ({gas_limit}) is greater than the cap ({cap})"
)
}
Self::CallGasCostMoreThanGasLimit {
initial_gas,
gas_limit,
} => {
write!(
f,
"call gas cost ({initial_gas}) exceeds the gas limit ({gas_limit})"
)
}
Self::GasFloorMoreThanGasLimit {
gas_floor,
gas_limit,
} => {
write!(
f,
"gas floor ({gas_floor}) exceeds the gas limit ({gas_limit})"
)
}
Self::RejectCallerWithCode => {
write!(f, "reject transactions from senders with deployed code")
}
Self::LackOfFundForMaxFee { fee, balance } => {
write!(f, "lack of funds ({balance}) for max fee ({fee})")
}
Self::OverflowPaymentInTransaction => {
write!(f, "overflow payment in transaction")
}
Self::NonceOverflowInTransaction => {
write!(f, "nonce overflow in transaction")
}
Self::NonceTooHigh { tx, state } => {
write!(f, "nonce {tx} too high, expected {state}")
}
Self::NonceTooLow { tx, state } => {
write!(f, "nonce {tx} too low, expected {state}")
}
Self::CreateInitCodeSizeLimit => {
write!(f, "create initcode size limit")
}
Self::InvalidChainId => write!(f, "invalid chain ID"),
Self::MissingChainId => write!(f, "missing chain ID"),
Self::AccessListNotSupported => write!(f, "access list not supported"),
Self::MaxFeePerBlobGasNotSupported => {
write!(f, "max fee per blob gas not supported")
}
Self::BlobVersionedHashesNotSupported => {
write!(f, "blob versioned hashes not supported")
}
Self::BlobGasPriceGreaterThanMax {
block_blob_gas_price,
tx_max_fee_per_blob_gas,
} => {
write!(
f,
"blob gas price ({block_blob_gas_price}) is greater than max fee per blob gas ({tx_max_fee_per_blob_gas})"
)
}
Self::EmptyBlobs => write!(f, "empty blobs"),
Self::BlobCreateTransaction => write!(f, "blob create transaction"),
Self::TooManyBlobs { max, have } => {
write!(f, "too many blobs, have {have}, max {max}")
}
Self::BlobVersionNotSupported => write!(f, "blob version not supported"),
Self::AuthorizationListNotSupported => write!(f, "authorization list not supported"),
Self::AuthorizationListInvalidFields => {
write!(f, "authorization list tx has invalid fields")
}
Self::EmptyAuthorizationList => write!(f, "empty authorization list"),
Self::Eip2930NotSupported => write!(f, "Eip2930 is not supported"),
Self::Eip1559NotSupported => write!(f, "Eip1559 is not supported"),
Self::Eip4844NotSupported => write!(f, "Eip4844 is not supported"),
Self::Eip7702NotSupported => write!(f, "Eip7702 is not supported"),
Self::Eip7873NotSupported => write!(f, "Eip7873 is not supported"),
Self::Eip7873MissingTarget => {
write!(f, "Eip7873 initcode transaction should have `to` address")
}
Self::Str(msg) => f.write_str(msg),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum InvalidHeader {
PrevrandaoNotSet,
ExcessBlobGasNotSet,
}
impl core::error::Error for InvalidHeader {}
impl fmt::Display for InvalidHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PrevrandaoNotSet => write!(f, "`prevrandao` not set"),
Self::ExcessBlobGasNotSet => write!(f, "`excess_blob_gas` not set"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SuccessReason {
Stop,
Return,
SelfDestruct,
}
impl fmt::Display for SuccessReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Stop => write!(f, "Stop"),
Self::Return => write!(f, "Return"),
Self::SelfDestruct => write!(f, "SelfDestruct"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HaltReason {
OutOfGas(OutOfGasError),
OpcodeNotFound,
InvalidFEOpcode,
InvalidJump,
NotActivated,
StackUnderflow,
StackOverflow,
OutOfOffset,
CreateCollision,
PrecompileError,
PrecompileErrorWithContext(String),
NonceOverflow,
CreateContractSizeLimit,
CreateContractStartingWithEF,
CreateInitCodeSizeLimit,
OverflowPayment,
StateChangeDuringStaticCall,
CallNotAllowedInsideStatic,
OutOfFunds,
CallTooDeep,
}
impl core::error::Error for HaltReason {}
impl fmt::Display for HaltReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OutOfGas(err) => write!(f, "{err}"),
Self::OpcodeNotFound => write!(f, "opcode not found"),
Self::InvalidFEOpcode => write!(f, "invalid 0xFE opcode"),
Self::InvalidJump => write!(f, "invalid jump destination"),
Self::NotActivated => write!(f, "feature or opcode not activated"),
Self::StackUnderflow => write!(f, "stack underflow"),
Self::StackOverflow => write!(f, "stack overflow"),
Self::OutOfOffset => write!(f, "out of offset"),
Self::CreateCollision => write!(f, "create collision"),
Self::PrecompileError => write!(f, "precompile error"),
Self::PrecompileErrorWithContext(msg) => write!(f, "precompile error: {msg}"),
Self::NonceOverflow => write!(f, "nonce overflow"),
Self::CreateContractSizeLimit => write!(f, "create contract size limit"),
Self::CreateContractStartingWithEF => {
write!(f, "create contract starting with 0xEF")
}
Self::CreateInitCodeSizeLimit => write!(f, "create initcode size limit"),
Self::OverflowPayment => write!(f, "overflow payment"),
Self::StateChangeDuringStaticCall => write!(f, "state change during static call"),
Self::CallNotAllowedInsideStatic => write!(f, "call not allowed inside static call"),
Self::OutOfFunds => write!(f, "out of funds"),
Self::CallTooDeep => write!(f, "call too deep"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum OutOfGasError {
Basic,
MemoryLimit,
Memory,
Precompile,
InvalidOperand,
ReentrancySentry,
}
impl core::error::Error for OutOfGasError {}
impl fmt::Display for OutOfGasError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Basic => write!(f, "out of gas"),
Self::MemoryLimit => write!(f, "out of gas: memory limit exceeded"),
Self::Memory => write!(f, "out of gas: memory expansion"),
Self::Precompile => write!(f, "out of gas: precompile"),
Self::InvalidOperand => write!(f, "out of gas: invalid operand"),
Self::ReentrancySentry => write!(f, "out of gas: reentrancy sentry"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TransactionIndexedError<Error> {
pub error: Error,
pub transaction_index: usize,
}
impl<Error> TransactionIndexedError<Error> {
#[must_use]
pub fn new(error: Error, transaction_index: usize) -> Self {
Self {
error,
transaction_index,
}
}
pub fn error(&self) -> &Error {
&self.error
}
#[must_use]
pub fn into_error(self) -> Error {
self.error
}
}
impl<Error: fmt::Display> fmt::Display for TransactionIndexedError<Error> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"transaction {} failed: {}",
self.transaction_index, self.error
)
}
}
impl<Error: core::error::Error + 'static> core::error::Error for TransactionIndexedError<Error> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.error)
}
}
impl From<&'static str> for InvalidTransaction {
fn from(s: &'static str) -> Self {
Self::Str(Cow::Borrowed(s))
}
}
impl From<String> for InvalidTransaction {
fn from(s: String) -> Self {
Self::Str(Cow::Owned(s))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execution_result_display() {
let result: ExecutionResult<HaltReason> = ExecutionResult::Success {
reason: SuccessReason::Return,
gas: ResultGas::default()
.with_total_gas_spent(100000)
.with_refunded(26000)
.with_floor_gas(5000),
logs: vec![Log::default(), Log::default()],
output: Output::Call(Bytes::from(vec![1, 2, 3])),
};
assert_eq!(
result.to_string(),
"Success (Return): Gas used: 74000, total spent: 100000, refunded: 26000, floor: 5000, 2 logs, 3 bytes output"
);
let result: ExecutionResult<HaltReason> = ExecutionResult::Revert {
gas: ResultGas::default()
.with_total_gas_spent(100000)
.with_refunded(100000),
logs: vec![],
output: Bytes::from(vec![1, 2, 3, 4]),
};
assert_eq!(
result.to_string(),
"Revert: Gas used: 0, total spent: 100000, refunded: 100000, 4 bytes output"
);
let result: ExecutionResult<HaltReason> = ExecutionResult::Halt {
reason: HaltReason::OutOfGas(OutOfGasError::Basic),
gas: ResultGas::default()
.with_total_gas_spent(1000000)
.with_refunded(1000000),
logs: vec![],
};
assert_eq!(
result.to_string(),
"Halted: out of gas (Gas used: 0, total spent: 1000000, refunded: 1000000)"
);
}
#[test]
fn test_result_gas_display() {
assert_eq!(
ResultGas::default().with_total_gas_spent(21000).to_string(),
"Gas used: 21000, total spent: 21000"
);
assert_eq!(
ResultGas::default()
.with_total_gas_spent(50000)
.with_refunded(10000)
.to_string(),
"Gas used: 40000, total spent: 50000, refunded: 10000"
);
assert_eq!(
ResultGas::default()
.with_total_gas_spent(50000)
.with_refunded(10000)
.with_floor_gas(30000)
.to_string(),
"Gas used: 40000, total spent: 50000, refunded: 10000, floor: 30000"
);
}
#[test]
fn test_result_gas_used_and_remaining() {
let gas = ResultGas::default()
.with_total_gas_spent(100)
.with_refunded(30);
assert_eq!(gas.total_gas_spent(), 100);
assert_eq!(gas.inner_refunded(), 30);
assert_eq!(gas.spent_sub_refunded(), 70);
let gas = ResultGas::default()
.with_total_gas_spent(10)
.with_refunded(50);
assert_eq!(gas.spent_sub_refunded(), 0);
}
#[test]
fn test_final_refunded_with_floor_gas() {
let gas = ResultGas::default()
.with_total_gas_spent(50000)
.with_refunded(10000);
assert_eq!(gas.tx_gas_used(), 40000);
assert_eq!(gas.final_refunded(), 10000);
let gas = ResultGas::default()
.with_total_gas_spent(50000)
.with_refunded(10000)
.with_floor_gas(45000);
assert_eq!(gas.tx_gas_used(), 45000);
assert_eq!(gas.final_refunded(), 0);
let gas = ResultGas::default()
.with_total_gas_spent(50000)
.with_refunded(10000)
.with_floor_gas(30000);
assert_eq!(gas.tx_gas_used(), 40000);
assert_eq!(gas.final_refunded(), 10000);
let gas = ResultGas::default()
.with_total_gas_spent(50000)
.with_refunded(10000)
.with_floor_gas(40000);
assert_eq!(gas.tx_gas_used(), 40000);
assert_eq!(gas.final_refunded(), 10000);
}
}