tempo_revm/
error.rs

1//! Tempo-specific transaction validation errors.
2
3use alloy_evm::error::InvalidTxError;
4use alloy_primitives::{Address, U256};
5use revm::context::result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction};
6
7/// Tempo-specific invalid transaction errors.
8///
9/// This enum extends the standard Ethereum [`InvalidTransaction`] with Tempo-specific
10/// validation errors that occur during transaction processing.
11#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
12pub enum TempoInvalidTransaction {
13    /// Standard Ethereum transaction validation error.
14    #[error(transparent)]
15    EthInvalidTransaction(#[from] InvalidTransaction),
16
17    /// System transaction must be a call (not a create).
18    #[error("system transaction must be a call, not a create")]
19    SystemTransactionMustBeCall,
20
21    /// System transaction execution failed.
22    #[error("system transaction execution failed, result: {_0:?}")]
23    SystemTransactionFailed(ExecutionResult<TempoHaltReason>),
24
25    /// Fee payer signature recovery failed.
26    ///
27    /// This error occurs when a transaction specifies a fee payer but the
28    /// signature recovery for the fee payer fails.
29    #[error("fee payer signature recovery failed")]
30    InvalidFeePayerSignature,
31
32    // Tempo transaction errors
33    /// Transaction cannot be included before validAfter timestamp.
34    ///
35    /// Tempo transactions can specify a validAfter field to restrict when they can be included.
36    #[error(
37        "transaction not valid yet: current block timestamp {current} < validAfter {valid_after}"
38    )]
39    ValidAfter {
40        /// The current block timestamp.
41        current: u64,
42        /// The validAfter constraint from the transaction.
43        valid_after: u64,
44    },
45
46    /// Transaction cannot be included after validBefore timestamp.
47    ///
48    /// Tempo transactions can specify a validBefore field to restrict when they can be included.
49    #[error("transaction expired: current block timestamp {current} >= validBefore {valid_before}")]
50    ValidBefore {
51        /// The current block timestamp.
52        current: u64,
53        /// The validBefore constraint from the transaction.
54        valid_before: u64,
55    },
56
57    /// P256 signature verification failed.
58    ///
59    /// The P256 signature could not be verified against the transaction hash.
60    #[error("P256 signature verification failed")]
61    InvalidP256Signature,
62
63    /// WebAuthn signature verification failed.
64    ///
65    /// The WebAuthn signature validation failed (could be authenticatorData, clientDataJSON, or P256 verification).
66    #[error("WebAuthn signature verification failed: {reason}")]
67    InvalidWebAuthnSignature {
68        /// Specific reason for failure.
69        reason: String,
70    },
71
72    /// Insufficient gas for intrinsic cost.
73    ///
74    /// Tempo transactions have variable intrinsic gas costs based on signature type and nonce usage.
75    /// This error occurs when the gas_limit is less than the calculated intrinsic gas.
76    #[error(
77        "insufficient gas for intrinsic cost: gas_limit {gas_limit} < intrinsic_gas {intrinsic_gas}"
78    )]
79    InsufficientGasForIntrinsicCost {
80        /// The transaction's gas limit.
81        gas_limit: u64,
82        /// The calculated intrinsic gas required.
83        intrinsic_gas: u64,
84    },
85
86    /// Nonce manager error.
87    #[error("nonce manager error: {0}")]
88    NonceManagerError(String),
89
90    /// Subblock transaction must have zero fee.
91    #[error("subblock transaction must have zero fee")]
92    SubblockTransactionMustHaveZeroFee,
93
94    /// Invalid fee token.
95    #[error("invalid fee token: {0}")]
96    InvalidFeeToken(Address),
97
98    /// Value transfer not allowed.
99    #[error("value transfer not allowed")]
100    ValueTransferNotAllowed,
101
102    /// Value transfer in Tempo Transaction not allowed.
103    #[error("value transfer in Tempo Transaction not allowed")]
104    ValueTransferNotAllowedInAATx,
105
106    /// Access key authorization failed.
107    ///
108    /// This error occurs when attempting to authorize an access key with the AccountKeychain
109    /// precompile fails (e.g., key already exists, invalid parameters, unauthorized caller).
110    #[error("access key authorization failed: {reason}")]
111    AccessKeyAuthorizationFailed {
112        /// Specific reason for failure.
113        reason: String,
114    },
115
116    /// Keychain operations are only supported after Allegretto.
117    #[error("keychain operations are only supported after Allegretto")]
118    KeychainOpBeforeAllegretto,
119
120    /// KeyAuthorization chain_id does not match the current chain.
121    #[error("KeyAuthorization chain_id mismatch: expected {expected}, got {got}")]
122    KeyAuthorizationChainIdMismatch {
123        /// The expected chain ID (current chain).
124        expected: u64,
125        /// The chain ID from the KeyAuthorization.
126        got: u64,
127    },
128
129    /// Keychain operations are not supported in subblock transactions.
130    #[error("keychain operations are not supported in subblock transactions")]
131    KeychainOpInSubblockTransaction,
132
133    /// Fee payment error.
134    #[error(transparent)]
135    CollectFeePreTx(#[from] FeePaymentError),
136}
137
138impl InvalidTxError for TempoInvalidTransaction {
139    fn is_nonce_too_low(&self) -> bool {
140        match self {
141            Self::EthInvalidTransaction(err) => err.is_nonce_too_low(),
142            _ => false,
143        }
144    }
145
146    fn as_invalid_tx_err(&self) -> Option<&InvalidTransaction> {
147        match self {
148            Self::EthInvalidTransaction(err) => Some(err),
149            _ => None,
150        }
151    }
152}
153
154impl<DBError> From<TempoInvalidTransaction> for EVMError<DBError, TempoInvalidTransaction> {
155    fn from(err: TempoInvalidTransaction) -> Self {
156        Self::Transaction(err)
157    }
158}
159
160/// Error type for fee payment errors.
161#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
162pub enum FeePaymentError {
163    /// Insufficient liquidity in the FeeAMM pool to perform fee token swap.
164    ///
165    /// This indicates the user's fee token cannot be swapped for the native token
166    /// because there's insufficient liquidity in the AMM pool.
167    #[error("insufficient liquidity in FeeAMM pool to swap fee tokens (required: {fee})")]
168    InsufficientAmmLiquidity {
169        /// The required fee amount that couldn't be swapped.
170        fee: U256,
171    },
172
173    /// Insufficient fee token balance to pay for transaction fees.
174    ///
175    /// This is distinct from the Ethereum `LackOfFundForMaxFee` error because
176    /// it applies to custom fee tokens, not native balance.
177    #[error("insufficient fee token balance: required {fee}, but only have {balance}")]
178    InsufficientFeeTokenBalance {
179        /// The required fee amount.
180        fee: U256,
181        /// The actual balance available.
182        balance: U256,
183    },
184
185    /// Other error.
186    #[error("{0}")]
187    Other(String),
188}
189
190impl<DBError> From<FeePaymentError> for EVMError<DBError, TempoInvalidTransaction> {
191    fn from(err: FeePaymentError) -> Self {
192        TempoInvalidTransaction::from(err).into()
193    }
194}
195
196/// Tempo-specific halt reason.
197///
198/// Used to extend basic [`HaltReason`] with an edge case of a subblock transaction fee payment error.
199#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From)]
200pub enum TempoHaltReason {
201    /// Basic Ethereum halt reason.
202    #[from]
203    Ethereum(HaltReason),
204    /// Subblock transaction failed to pay fees.
205    SubblockTxFeePayment,
206}
207
208#[cfg(feature = "rpc")]
209impl reth_rpc_eth_types::error::api::FromEvmHalt<TempoHaltReason>
210    for reth_rpc_eth_types::EthApiError
211{
212    fn from_evm_halt(halt_reason: TempoHaltReason, gas_limit: u64) -> Self {
213        match halt_reason {
214            TempoHaltReason::Ethereum(halt_reason) => Self::from_evm_halt(halt_reason, gas_limit),
215            TempoHaltReason::SubblockTxFeePayment => {
216                Self::EvmCustom("subblock transaction failed to pay fees".to_string())
217            }
218        }
219    }
220}
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn test_error_display() {
227        let err = TempoInvalidTransaction::SystemTransactionMustBeCall;
228        assert_eq!(
229            err.to_string(),
230            "system transaction must be a call, not a create"
231        );
232
233        let err = FeePaymentError::InsufficientAmmLiquidity {
234            fee: U256::from(1000),
235        };
236        assert!(
237            err.to_string()
238                .contains("insufficient liquidity in FeeAMM pool")
239        );
240
241        let err = FeePaymentError::InsufficientFeeTokenBalance {
242            fee: U256::from(1000),
243            balance: U256::from(500),
244        };
245        assert!(err.to_string().contains("insufficient fee token balance"));
246    }
247
248    #[test]
249    fn test_from_invalid_transaction() {
250        let eth_err = InvalidTransaction::PriorityFeeGreaterThanMaxFee;
251        let tempo_err: TempoInvalidTransaction = eth_err.into();
252        assert!(matches!(
253            tempo_err,
254            TempoInvalidTransaction::EthInvalidTransaction(_)
255        ));
256    }
257}