1use alloy_evm::error::InvalidTxError;
4use alloy_primitives::{Address, U256};
5use revm::context::result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction};
6use tempo_primitives::transaction::{KeyAuthorizationChainIdError, KeychainVersionError};
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
13pub enum TempoInvalidTransaction {
14 #[error(transparent)]
16 EthInvalidTransaction(#[from] InvalidTransaction),
17
18 #[error("system transaction must be a call, not a create")]
20 SystemTransactionMustBeCall,
21
22 #[error("system transaction execution failed, result: {_0:?}")]
24 SystemTransactionFailed(Box<ExecutionResult<TempoHaltReason>>),
25
26 #[error("fee payer signature recovery failed")]
31 InvalidFeePayerSignature,
32
33 #[error("fee payer cannot resolve to sender")]
35 SelfSponsoredFeePayer,
36
37 #[error(
42 "transaction not valid yet: current block timestamp {current} < validAfter {valid_after}"
43 )]
44 ValidAfter {
45 current: u64,
47 valid_after: u64,
49 },
50
51 #[error("transaction expired: current block timestamp {current} >= validBefore {valid_before}")]
55 ValidBefore {
56 current: u64,
58 valid_before: u64,
60 },
61
62 #[error("P256 signature verification failed")]
66 InvalidP256Signature,
67
68 #[error("WebAuthn signature verification failed: {reason}")]
72 InvalidWebAuthnSignature {
73 reason: String,
75 },
76
77 #[error(
82 "insufficient gas for intrinsic cost: gas_limit {gas_limit} < intrinsic_gas {intrinsic_gas}"
83 )]
84 InsufficientGasForIntrinsicCost {
85 gas_limit: u64,
87 intrinsic_gas: u64,
89 },
90
91 #[error("nonce manager error: {0}")]
93 NonceManagerError(String),
94
95 #[error("expiring nonce transaction requires tempo_tx_env")]
97 ExpiringNonceMissingTxEnv,
98
99 #[error("expiring nonce transaction requires valid_before to be set")]
101 ExpiringNonceMissingValidBefore,
102
103 #[error("expiring nonce transaction must have nonce == 0")]
105 ExpiringNonceNonceNotZero,
106
107 #[error("subblock transaction must have zero fee")]
109 SubblockTransactionMustHaveZeroFee,
110
111 #[error("invalid fee token: {0}")]
113 InvalidFeeToken(Address),
114
115 #[error("value transfer not allowed")]
117 ValueTransferNotAllowed,
118
119 #[error("value transfer in Tempo Transaction not allowed")]
121 ValueTransferNotAllowedInAATx,
122
123 #[error("failed to recover access key address from signature")]
127 AccessKeyRecoveryFailed,
128
129 #[error("access keys cannot authorize other keys, only the root key can authorize new keys")]
134 AccessKeyCannotAuthorizeOtherKeys,
135
136 #[error("failed to recover signer from KeyAuthorization signature")]
140 KeyAuthorizationSignatureRecoveryFailed,
141
142 #[error(
147 "KeyAuthorization must be signed by root account {expected}, but was signed by {actual}"
148 )]
149 KeyAuthorizationNotSignedByRoot {
150 expected: Address,
152 actual: Address,
154 },
155
156 #[error("access key expiry {expiry} is in the past (current timestamp: {current_timestamp})")]
160 AccessKeyExpiryInPast {
161 expiry: u64,
163 current_timestamp: u64,
165 },
166
167 #[error("keychain precompile error: {reason}")]
172 KeychainPrecompileError {
173 reason: String,
175 },
176
177 #[error("keychain user_address {user_address} does not match transaction caller {caller}")]
181 KeychainUserAddressMismatch {
182 user_address: Address,
184 caller: Address,
186 },
187
188 #[error("keychain validation failed: {reason}")]
193 KeychainValidationFailed {
194 reason: String,
196 },
197
198 #[error("KeyAuthorization chain_id mismatch: expected {expected}, got {got}")]
200 KeyAuthorizationChainIdMismatch {
201 expected: u64,
203 got: u64,
205 },
206
207 #[error("legacy V1 keychain signature is no longer accepted, use V2 (type 0x04)")]
212 LegacyKeychainSignature,
213
214 #[error("V2 keychain signature (type 0x04) is not valid before T1C activation")]
222 V2KeychainBeforeActivation,
223
224 #[error("keychain operations are not supported in subblock transactions")]
226 KeychainOpInSubblockTransaction,
227
228 #[error(transparent)]
230 CollectFeePreTx(#[from] FeePaymentError),
231
232 #[error("{0}")]
236 CallsValidation(&'static str),
237}
238
239impl InvalidTxError for TempoInvalidTransaction {
240 fn is_nonce_too_low(&self) -> bool {
241 match self {
242 Self::EthInvalidTransaction(err) => err.is_nonce_too_low(),
243 _ => false,
244 }
245 }
246
247 fn as_invalid_tx_err(&self) -> Option<&InvalidTransaction> {
248 match self {
249 Self::EthInvalidTransaction(err) => Some(err),
250 _ => None,
251 }
252 }
253}
254
255impl<DBError> From<TempoInvalidTransaction> for EVMError<DBError, TempoInvalidTransaction> {
256 fn from(err: TempoInvalidTransaction) -> Self {
257 Self::Transaction(err)
258 }
259}
260
261impl From<&'static str> for TempoInvalidTransaction {
262 fn from(err: &'static str) -> Self {
263 Self::CallsValidation(err)
264 }
265}
266
267impl From<KeychainVersionError> for TempoInvalidTransaction {
268 fn from(err: KeychainVersionError) -> Self {
269 match err {
270 KeychainVersionError::LegacyPostT1C => Self::LegacyKeychainSignature,
271 KeychainVersionError::V2BeforeActivation => Self::V2KeychainBeforeActivation,
272 }
273 }
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
278pub enum FeePaymentError {
279 #[error("insufficient liquidity in FeeAMM pool to swap fee tokens (required: {fee})")]
284 InsufficientAmmLiquidity {
285 fee: U256,
287 },
288
289 #[error("insufficient fee token balance: required {fee}, but only have {balance}")]
294 InsufficientFeeTokenBalance {
295 fee: U256,
297 balance: U256,
299 },
300
301 #[error("{0}")]
303 Other(String),
304}
305
306impl From<KeyAuthorizationChainIdError> for TempoInvalidTransaction {
307 fn from(err: KeyAuthorizationChainIdError) -> Self {
308 Self::KeyAuthorizationChainIdMismatch {
309 expected: err.expected,
310 got: err.got,
311 }
312 }
313}
314
315impl<DBError> From<FeePaymentError> for EVMError<DBError, TempoInvalidTransaction> {
316 fn from(err: FeePaymentError) -> Self {
317 TempoInvalidTransaction::from(err).into()
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From)]
325pub enum TempoHaltReason {
326 #[from]
328 Ethereum(HaltReason),
329 SubblockTxFeePayment,
331}
332
333#[cfg(feature = "rpc")]
334impl reth_rpc_eth_types::error::api::FromEvmHalt<TempoHaltReason>
335 for reth_rpc_eth_types::EthApiError
336{
337 fn from_evm_halt(halt_reason: TempoHaltReason, gas_limit: u64) -> Self {
338 match halt_reason {
339 TempoHaltReason::Ethereum(halt_reason) => Self::from_evm_halt(halt_reason, gas_limit),
340 TempoHaltReason::SubblockTxFeePayment => {
341 Self::EvmCustom("subblock transaction failed to pay fees".to_string())
342 }
343 }
344 }
345}
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn test_error_display() {
352 let err = TempoInvalidTransaction::SystemTransactionMustBeCall;
353 assert_eq!(
354 err.to_string(),
355 "system transaction must be a call, not a create"
356 );
357
358 let err = FeePaymentError::InsufficientAmmLiquidity {
359 fee: U256::from(1000),
360 };
361 assert!(
362 err.to_string()
363 .contains("insufficient liquidity in FeeAMM pool")
364 );
365
366 let err = FeePaymentError::InsufficientFeeTokenBalance {
367 fee: U256::from(1000),
368 balance: U256::from(500),
369 };
370 assert!(err.to_string().contains("insufficient fee token balance"));
371 }
372
373 #[test]
374 fn test_from_invalid_transaction() {
375 let eth_err = InvalidTransaction::PriorityFeeGreaterThanMaxFee;
376 let tempo_err: TempoInvalidTransaction = eth_err.into();
377 assert!(matches!(
378 tempo_err,
379 TempoInvalidTransaction::EthInvalidTransaction(_)
380 ));
381 }
382
383 #[test]
384 fn test_is_nonce_too_low() {
385 let err = TempoInvalidTransaction::EthInvalidTransaction(InvalidTransaction::NonceTooLow {
386 tx: 1,
387 state: 0,
388 });
389 assert!(err.is_nonce_too_low());
390 assert!(err.as_invalid_tx_err().is_some());
391
392 let err = TempoInvalidTransaction::InvalidFeePayerSignature;
393 assert!(!err.is_nonce_too_low());
394 assert!(err.as_invalid_tx_err().is_none());
395
396 let err = TempoInvalidTransaction::SelfSponsoredFeePayer;
397 assert!(!err.is_nonce_too_low());
398 assert!(err.as_invalid_tx_err().is_none());
399 }
400
401 #[test]
402 fn test_fee_payment_error() {
403 let _: EVMError<(), TempoInvalidTransaction> = FeePaymentError::InsufficientAmmLiquidity {
404 fee: U256::from(1000),
405 }
406 .into();
407 }
408}