1use crate::{
2 amm::AmmLiquidityCache,
3 state_cache::{StateCache, StateCacheDb},
4 transaction::{TempoPoolTransactionError, TempoPooledTransaction},
5};
6
7use alloy_consensus::constants::KECCAK_EMPTY;
8use alloy_evm::{Database, EvmEnv};
9use alloy_primitives::{Address, B256};
10use parking_lot::RwLock;
11use reth_chainspec::ChainSpecProvider;
12use reth_evm::ConfigureEvm;
13use reth_primitives_traits::{
14 Account, Bytecode, SealedBlock, transaction::error::InvalidTransactionError,
15};
16use reth_provider::BlockReaderIdExt;
17use reth_revm::database::StateProviderDatabase;
18use reth_storage_api::{
19 AccountReader, BytecodeReader, StateProvider, StateProviderBox, StateProviderFactory,
20 errors::{ProviderError, ProviderResult},
21};
22use reth_transaction_pool::{
23 EthTransactionValidator, PoolTransaction, TransactionOrigin, TransactionValidationOutcome,
24 TransactionValidator, error::InvalidPoolTransactionError,
25};
26use revm::{
27 DatabaseRef,
28 context::{
29 ContextTr, JournalTr,
30 result::{EVMError, InvalidTransaction},
31 },
32};
33use std::sync::{
34 Arc,
35 atomic::{AtomicU8, Ordering},
36};
37use tempo_chainspec::{
38 TempoChainSpec,
39 hardfork::{TempoHardfork, TempoHardforks},
40};
41use tempo_evm::{TempoEvmConfig, evm::TempoEvm};
42use tempo_precompiles::nonce::{INonce, NonceManager};
43use tempo_primitives::{
44 Block, TempoHeader,
45 subblock::has_sub_block_nonce_key_prefix,
46 transaction::{TEMPO_EXPIRING_NONCE_KEY, TempoTransaction},
47};
48use tempo_revm::{
49 TempoBlockEnv, TempoInvalidTransaction, TempoStateAccess, error::FeePaymentError,
50};
51
52const AA_VALID_BEFORE_MIN_SECS: u64 = 3;
54
55pub const DEFAULT_MAX_TEMPO_AUTHORIZATIONS: usize = 16;
57
58pub const MAX_AA_CALLS: usize = 32;
60
61pub const MAX_CALL_INPUT_SIZE: usize = 128 * 1024;
63
64pub const MAX_ACCESS_LIST_ACCOUNTS: usize = 256;
66
67pub const MAX_STORAGE_KEYS_PER_ACCOUNT: usize = 256;
69
70pub const MAX_ACCESS_LIST_STORAGE_KEYS_TOTAL: usize = 2048;
72
73pub const MAX_TOKEN_LIMITS: usize = 256;
75
76pub const DEFAULT_AA_VALID_AFTER_MAX_SECS: u64 = 120;
82
83const MAX_KEYCHAIN_CALL_SCOPES: u8 = 64;
85const MAX_KEYCHAIN_SELECTOR_RULES_PER_SCOPE: u8 = 64;
87const MAX_KEYCHAIN_RECIPIENTS_PER_SELECTOR: u8 = 64;
89
90#[derive(Debug)]
92pub struct TempoTransactionValidator<Client> {
93 pub(crate) inner: EthTransactionValidator<Client, TempoPooledTransaction, TempoEvmConfig>,
95 pub(crate) aa_valid_after_max_secs: u64,
97 pub(crate) max_tempo_authorizations: usize,
99 pub(crate) amm_liquidity_cache: AmmLiquidityCache,
101 cached_evm_env: RwLock<EvmEnv<TempoHardfork, TempoBlockEnv>>,
103 cached_state: RwLock<(B256, Arc<StateCache>)>,
106 active_hardfork: AtomicU8,
112}
113
114impl<Client> TempoTransactionValidator<Client>
115where
116 Client: ChainSpecProvider<ChainSpec = TempoChainSpec> + StateProviderFactory,
117{
118 pub fn new(
119 inner: EthTransactionValidator<Client, TempoPooledTransaction, TempoEvmConfig>,
120 aa_valid_after_max_secs: u64,
121 max_tempo_authorizations: usize,
122 amm_liquidity_cache: AmmLiquidityCache,
123 ) -> Self
124 where
125 Client: BlockReaderIdExt<Header = TempoHeader>,
126 {
127 let latest_header = inner
128 .client()
129 .latest_header()
130 .expect("failed to fetch latest header")
131 .expect("latest header is None");
132 let evm_env = inner
133 .evm_config()
134 .evm_env(latest_header.header())
135 .expect("failed constructing EvmEnv from latest header");
136 let active_hardfork = AtomicU8::new(evm_env.cfg_env.spec.variant_index());
137 Self {
138 inner,
139 aa_valid_after_max_secs,
140 max_tempo_authorizations,
141 amm_liquidity_cache,
142 cached_evm_env: parking_lot::RwLock::new(evm_env),
143 cached_state: RwLock::new((latest_header.hash(), Arc::new(StateCache::default()))),
144 active_hardfork,
145 }
146 }
147
148 pub fn active_hardfork(&self) -> TempoHardfork {
152 TempoHardfork::from_variant_index(self.active_hardfork.load(Ordering::Relaxed))
153 .expect("stored hardfork index is valid")
154 }
155
156 pub fn amm_liquidity_cache(&self) -> AmmLiquidityCache {
158 self.amm_liquidity_cache.clone()
159 }
160
161 pub fn client(&self) -> &Client {
163 self.inner.client()
164 }
165
166 fn ensure_pool_time_bounds(
173 &self,
174 tx: &TempoTransaction,
175 ) -> Result<(), TempoPoolTransactionError> {
176 let tip_timestamp = self.inner.fork_tracker().tip_timestamp();
177
178 if let Some(valid_before) = tx.valid_before {
182 let valid_before = valid_before.get();
183 let min_allowed = tip_timestamp.saturating_add(AA_VALID_BEFORE_MIN_SECS);
184 if valid_before <= min_allowed {
185 return Err(TempoPoolTransactionError::InvalidValidBefore {
186 valid_before,
187 min_allowed,
188 });
189 }
190 }
191
192 if let Some(valid_after) = tx.valid_after {
195 let valid_after = valid_after.get();
196 let current_time = std::time::SystemTime::now()
197 .duration_since(std::time::UNIX_EPOCH)
198 .map(|d| d.as_secs())
199 .unwrap_or(0);
200 let max_allowed = current_time.saturating_add(self.aa_valid_after_max_secs);
201 if valid_after > max_allowed {
202 return Err(TempoPoolTransactionError::InvalidValidAfter {
203 valid_after,
204 max_allowed,
205 });
206 }
207 }
208
209 Ok(())
210 }
211
212 fn ensure_authorization_list_size(
214 &self,
215 transaction: &TempoPooledTransaction,
216 ) -> Result<(), TempoPoolTransactionError> {
217 let Some(aa_tx) = transaction.inner().as_aa() else {
218 return Ok(());
219 };
220
221 let count = aa_tx.tx().tempo_authorization_list.len();
222 if count > self.max_tempo_authorizations {
223 return Err(TempoPoolTransactionError::TooManyAuthorizations {
224 count,
225 max_allowed: self.max_tempo_authorizations,
226 });
227 }
228
229 Ok(())
230 }
231 fn ensure_aa_field_limits(
237 &self,
238 transaction: &TempoPooledTransaction,
239 ) -> Result<(), TempoPoolTransactionError> {
240 let Some(aa_tx) = transaction.inner().as_aa() else {
241 return Ok(());
242 };
243
244 let tx = aa_tx.tx();
245
246 if tx.calls.len() > MAX_AA_CALLS {
248 return Err(TempoPoolTransactionError::TooManyCalls {
249 count: tx.calls.len(),
250 max_allowed: MAX_AA_CALLS,
251 });
252 }
253
254 for (idx, call) in tx.calls.iter().enumerate() {
256 if call.input.len() > MAX_CALL_INPUT_SIZE {
257 return Err(TempoPoolTransactionError::CallInputTooLarge {
258 call_index: idx,
259 size: call.input.len(),
260 max_allowed: MAX_CALL_INPUT_SIZE,
261 });
262 }
263 }
264
265 if tx.access_list.len() > MAX_ACCESS_LIST_ACCOUNTS {
267 return Err(TempoPoolTransactionError::TooManyAccessListAccounts {
268 count: tx.access_list.len(),
269 max_allowed: MAX_ACCESS_LIST_ACCOUNTS,
270 });
271 }
272
273 let mut total_storage_keys = 0usize;
275 for (idx, entry) in tx.access_list.iter().enumerate() {
276 if entry.storage_keys.len() > MAX_STORAGE_KEYS_PER_ACCOUNT {
277 return Err(TempoPoolTransactionError::TooManyStorageKeysPerAccount {
278 account_index: idx,
279 count: entry.storage_keys.len(),
280 max_allowed: MAX_STORAGE_KEYS_PER_ACCOUNT,
281 });
282 }
283 total_storage_keys = total_storage_keys.saturating_add(entry.storage_keys.len());
284 }
285
286 if total_storage_keys > MAX_ACCESS_LIST_STORAGE_KEYS_TOTAL {
287 return Err(TempoPoolTransactionError::TooManyTotalStorageKeys {
288 count: total_storage_keys,
289 max_allowed: MAX_ACCESS_LIST_STORAGE_KEYS_TOTAL,
290 });
291 }
292
293 if let Some(ref key_auth) = tx.key_authorization {
297 if let Some(limits) = &key_auth.limits
298 && limits.len() > MAX_TOKEN_LIMITS
299 {
300 return Err(TempoPoolTransactionError::TooManyTokenLimits {
301 count: limits.len(),
302 max_allowed: MAX_TOKEN_LIMITS,
303 });
304 }
305
306 if let Some(scopes) = &key_auth.allowed_calls {
307 if scopes.len() > MAX_KEYCHAIN_CALL_SCOPES as usize {
308 return Err(TempoPoolTransactionError::Keychain(
309 "too many call scopes in key authorization",
310 ));
311 }
312
313 for scope in scopes {
314 if scope.selector_rules.len() > MAX_KEYCHAIN_SELECTOR_RULES_PER_SCOPE as usize {
315 return Err(TempoPoolTransactionError::Keychain(
316 "too many selector rules in call scope",
317 ));
318 }
319
320 for rule in &scope.selector_rules {
321 if rule.recipients.len() > MAX_KEYCHAIN_RECIPIENTS_PER_SELECTOR as usize {
322 return Err(TempoPoolTransactionError::Keychain(
323 "too many recipients in selector rule",
324 ));
325 }
326 }
327 }
328 }
329 }
330
331 Ok(())
332 }
333
334 fn validate_batch<P: StateProvider>(
341 &self,
342 state_provider: P,
343 cached_state: Arc<StateCache>,
344 transactions: impl IntoIterator<Item = (TransactionOrigin, TempoPooledTransaction)>,
345 ) -> Vec<TransactionValidationOutcome<TempoPooledTransaction>> {
346 let mut db = StateCacheDb::new(&cached_state, StateProviderDatabase::new(&state_provider));
347 let evm_env = self.cached_evm_env.read().clone();
348
349 let mut evm = TempoEvm::new(&mut db, evm_env);
357 evm.inner_mut().skip_valid_after_check = true;
358 evm.inner_mut().skip_liquidity_check = true;
359 evm.ctx_mut().cfg.disable_nonce_check = true;
360
361 transactions
362 .into_iter()
363 .map(|(origin, transaction)| {
364 let outcome = self.validate_one_with_evm(origin, transaction, &mut evm);
365 evm.ctx_mut().journal_mut().discard_tx();
369 outcome
370 })
371 .collect()
372 }
373
374 fn latest_state_provider_and_cache(
376 &self,
377 ) -> ProviderResult<(StateProviderBox, Arc<StateCache>)> {
378 let state_provider = self.inner.client().latest()?;
379 let latest_hash = self.inner.client().chain_info()?.best_hash;
380 Ok((state_provider, self.state_cache_for_tip(latest_hash)))
381 }
382
383 fn state_cache_for_tip(&self, tip_hash: B256) -> Arc<StateCache> {
388 let (cached_tip_hash, cached_state) = self.cached_state.read().clone();
389 if cached_tip_hash == tip_hash {
390 cached_state
391 } else {
392 Arc::new(StateCache::default())
393 }
394 }
395
396 fn validate_one_with_evm<DB>(
400 &self,
401 origin: TransactionOrigin,
402 transaction: TempoPooledTransaction,
403 evm: &mut TempoEvm<DB>,
404 ) -> TransactionValidationOutcome<TempoPooledTransaction>
405 where
406 DB: Database<Error = ProviderError> + DatabaseRef<Error = ProviderError>,
407 {
408 let spec = self.active_hardfork();
410
411 if transaction.inner().is_system_tx() {
413 return TransactionValidationOutcome::Invalid(
414 transaction,
415 InvalidPoolTransactionError::Consensus(InvalidTransactionError::TxTypeNotSupported),
416 );
417 }
418
419 let tx_size = transaction.encoded_length();
421 let max_size = self.inner.max_tx_input_bytes();
422 if tx_size > max_size {
423 return TransactionValidationOutcome::Invalid(
424 transaction,
425 InvalidPoolTransactionError::OversizedData {
426 size: tx_size,
427 limit: max_size,
428 },
429 );
430 }
431
432 if let Err(err) = self.ensure_authorization_list_size(&transaction) {
434 return TransactionValidationOutcome::Invalid(
435 transaction,
436 InvalidPoolTransactionError::other(err),
437 );
438 }
439
440 if let Err(err) = self.ensure_aa_field_limits(&transaction) {
442 return TransactionValidationOutcome::Invalid(
443 transaction,
444 InvalidPoolTransactionError::other(err),
445 );
446 }
447
448 if let Some(tx) = transaction.inner().as_aa()
450 && let Err(err) = self.ensure_pool_time_bounds(tx.tx())
451 {
452 return TransactionValidationOutcome::Invalid(
453 transaction,
454 InvalidPoolTransactionError::other(err),
455 );
456 }
457
458 let result = if let Some(tx_env) = transaction.cached_tx_env() {
465 evm.validate_transaction(tx_env.clone())
466 } else {
467 let result = evm.validate_transaction(transaction.tx_env_slow());
468 transaction.cache_tx_env(core::mem::take(&mut evm.ctx_mut().tx));
469 result
470 };
471 let validation_ctx = match result {
472 Ok(ctx) => ctx,
473 Err(err) => match err {
474 EVMError::Transaction(err) => {
475 let err = match err {
476 TempoInvalidTransaction::EthInvalidTransaction(
477 InvalidTransaction::LackOfFundForMaxFee { fee, balance },
478 ) => InvalidPoolTransactionError::Consensus(
479 InvalidTransactionError::InsufficientFunds((*balance, *fee).into()),
480 ),
481 err => {
482 InvalidPoolTransactionError::other(TempoPoolTransactionError::Evm(err))
483 }
484 };
485 return TransactionValidationOutcome::Invalid(transaction, err);
486 }
487 other => {
488 return TransactionValidationOutcome::Error(
489 *transaction.hash(),
490 Box::new(other),
491 );
492 }
493 },
494 };
495
496 transaction.set_resolved_fee_token(validation_ctx.fee_token);
498
499 if let Some(key_expiry) = validation_ctx.key_expiry {
502 let min_allowed = self
503 .inner
504 .fork_tracker()
505 .tip_timestamp()
506 .saturating_add(AA_VALID_BEFORE_MIN_SECS);
507 if key_expiry <= min_allowed {
508 return TransactionValidationOutcome::Invalid(
509 transaction,
510 InvalidPoolTransactionError::other(
511 TempoPoolTransactionError::AccessKeyExpired {
512 expiry: key_expiry,
513 min_allowed,
514 },
515 ),
516 );
517 }
518
519 transaction.set_key_expiry(Some(key_expiry));
521 }
522
523 let fee = transaction.fee_token_cost();
525 match self.amm_liquidity_cache.has_enough_liquidity(
526 validation_ctx.fee_token,
527 fee,
528 evm.db_mut(),
529 ) {
530 Ok(true) => {}
531 Ok(false) => {
532 return TransactionValidationOutcome::Invalid(
533 transaction,
534 InvalidPoolTransactionError::other(TempoPoolTransactionError::Evm(
535 TempoInvalidTransaction::CollectFeePreTx(
536 FeePaymentError::InsufficientAmmLiquidity { fee },
537 ),
538 )),
539 );
540 }
541 Err(err) => {
542 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
543 }
544 }
545
546 let inner_validation = {
550 let cached_state_provider = CachedAccountInfoReader::new(evm.db_ref());
551 self.inner
552 .validate_one_with_state_provider(origin, transaction, &cached_state_provider)
553 };
554
555 match inner_validation {
556 TransactionValidationOutcome::Valid {
557 balance,
558 mut state_nonce,
559 bytecode_hash,
560 transaction,
561 propagate,
562 authorities,
563 } => {
564 let mut authorities = authorities;
565 if let Some(aa_tx) = transaction.transaction().inner().as_aa() {
566 let mut recovered_aa_authorities = aa_tx
567 .tx()
568 .tempo_authorization_list
569 .iter()
570 .filter_map(|authorization| authorization.recover_authority().ok())
571 .collect::<Vec<_>>();
572
573 if !recovered_aa_authorities.is_empty() {
574 match authorities.as_mut() {
575 Some(existing_authorities) => {
576 existing_authorities.append(&mut recovered_aa_authorities)
577 }
578 None => authorities = Some(recovered_aa_authorities),
579 }
580 }
581 }
582
583 if let Some(nonce_key) = transaction.transaction().nonce_key()
585 && !nonce_key.is_zero()
586 {
587 if has_sub_block_nonce_key_prefix(&nonce_key) {
589 return TransactionValidationOutcome::Invalid(
590 transaction.into_transaction(),
591 InvalidPoolTransactionError::other(
592 TempoPoolTransactionError::SubblockNonceKey,
593 ),
594 );
595 }
596
597 let current_time = self.inner.fork_tracker().tip_timestamp();
599 let is_t1_active = self
600 .inner
601 .chain_spec()
602 .is_t1_active_at_timestamp(current_time);
603
604 if is_t1_active && nonce_key == TEMPO_EXPIRING_NONCE_KEY {
605 } else {
607 state_nonce = match evm.db_mut().with_read_only_storage_ctx(spec, || {
609 NonceManager::new().get_nonce(INonce::getNonceCall {
610 account: transaction.transaction().sender(),
611 nonceKey: nonce_key,
612 })
613 }) {
614 Ok(nonce) => nonce,
615 Err(err) => {
616 return TransactionValidationOutcome::Error(
617 *transaction.hash(),
618 Box::new(err),
619 );
620 }
621 };
622 let tx_nonce = transaction.nonce();
623 if tx_nonce < state_nonce {
624 return TransactionValidationOutcome::Invalid(
625 transaction.into_transaction(),
626 InvalidTransactionError::NonceNotConsistent {
627 tx: tx_nonce,
628 state: state_nonce,
629 }
630 .into(),
631 );
632 }
633 }
634 }
635
636 transaction.transaction().fee_balance_slot();
638
639 let _ = transaction.transaction().expiring_nonce_slot();
641 let _ = transaction.transaction().nonce_key_slot();
642
643 transaction.transaction().precalculate_keccak_slots();
645
646 TransactionValidationOutcome::Valid {
647 balance,
648 state_nonce,
649 bytecode_hash,
650 transaction,
651 propagate,
652 authorities,
653 }
654 }
655 outcome => outcome,
656 }
657 }
658}
659
660impl<Client> TransactionValidator for TempoTransactionValidator<Client>
661where
662 Client: ChainSpecProvider<ChainSpec = TempoChainSpec> + StateProviderFactory,
663{
664 type Transaction = TempoPooledTransaction;
665 type Block = Block;
666
667 async fn validate_transaction(
668 &self,
669 origin: TransactionOrigin,
670 transaction: Self::Transaction,
671 ) -> TransactionValidationOutcome<Self::Transaction> {
672 let (state_provider, cached_state) = match self.latest_state_provider_and_cache() {
673 Ok(provider_and_cache) => provider_and_cache,
674 Err(err) => {
675 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
676 }
677 };
678
679 self.validate_batch(
680 state_provider,
681 cached_state,
682 core::iter::once((origin, transaction)),
683 )
684 .pop()
685 .expect("validate_batch returns one outcome per transaction")
686 }
687
688 async fn validate_transactions(
689 &self,
690 transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
691 + Send,
692 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
693 let (state_provider, cached_state) = match self.latest_state_provider_and_cache() {
694 Ok(provider_and_cache) => provider_and_cache,
695 Err(err) => {
696 return transactions
697 .into_iter()
698 .map(|(_, tx)| {
699 TransactionValidationOutcome::Error(*tx.hash(), Box::new(err.clone()))
700 })
701 .collect();
702 }
703 };
704
705 self.validate_batch(state_provider, cached_state, transactions)
706 }
707
708 async fn validate_transactions_with_origin(
709 &self,
710 origin: TransactionOrigin,
711 transactions: impl IntoIterator<Item = Self::Transaction> + Send,
712 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
713 let (state_provider, cached_state) = match self.latest_state_provider_and_cache() {
714 Ok(provider_and_cache) => provider_and_cache,
715 Err(err) => {
716 return transactions
717 .into_iter()
718 .map(|tx| {
719 TransactionValidationOutcome::Error(*tx.hash(), Box::new(err.clone()))
720 })
721 .collect();
722 }
723 };
724
725 self.validate_batch(
726 state_provider,
727 cached_state,
728 transactions.into_iter().map(|tx| (origin, tx)),
729 )
730 }
731
732 fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
733 self.inner.on_new_head_block(new_tip_block);
734
735 let evm_env = self
737 .inner
738 .evm_config()
739 .evm_env(new_tip_block.header())
740 .expect("invalid block in on_new_head_block");
741 self.active_hardfork
742 .store(evm_env.cfg_env.spec.variant_index(), Ordering::Relaxed);
743 *self.cached_evm_env.write() = evm_env;
744
745 *self.cached_state.write() = (new_tip_block.hash(), Arc::new(StateCache::default()));
747 }
748}
749
750struct CachedAccountInfoReader<DB> {
753 db: DB,
754}
755
756impl<DB> CachedAccountInfoReader<DB> {
757 const fn new(db: DB) -> Self {
758 Self { db }
759 }
760}
761
762impl<DB> AccountReader for CachedAccountInfoReader<DB>
763where
764 DB: DatabaseRef<Error = ProviderError>,
765{
766 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
767 Ok(self.db.basic_ref(*address)?.map(|account| Account {
768 nonce: account.nonce,
769 balance: account.balance,
770 bytecode_hash: (account.code_hash != KECCAK_EMPTY).then_some(account.code_hash),
771 }))
772 }
773}
774
775impl<DB> BytecodeReader for CachedAccountInfoReader<DB>
776where
777 DB: DatabaseRef<Error = ProviderError>,
778{
779 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
780 Ok(Some(Bytecode(self.db.code_by_hash_ref(*code_hash)?)))
781 }
782}
783
784#[cfg(test)]
785mod tests {
786 use super::*;
787 use crate::{test_utils::TxBuilder, transaction::TempoPoolTransactionError};
788 use alloy_consensus::{Header, Signed, Transaction, TxLegacy};
789 use alloy_primitives::{Address, B256, TxKind, U256, address, uint};
790 use alloy_signer::Signature;
791 use reth_chainspec::EthChainSpec;
792 use reth_primitives_traits::{Account, Bytecode, SignedTransaction};
793 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
794 use reth_revm::cached::CachedReads;
795 use reth_storage_api::{AccountReader, BlockNumReader, BytecodeReader};
796 use reth_transaction_pool::{
797 PoolTransaction, blobstore::InMemoryBlobStore, validate::EthTransactionValidatorBuilder,
798 };
799 use revm::{DatabaseRef, context::result::InvalidTransaction};
800 use std::sync::{
801 Arc,
802 atomic::{AtomicUsize, Ordering},
803 };
804 use tempo_chainspec::spec::{
805 MODERATO, TEMPO_T0_BASE_FEE, TEMPO_T1_BASE_FEE, TEMPO_T1_TX_GAS_LIMIT_CAP,
806 };
807 use tempo_precompiles::{
808 PATH_USD_ADDRESS,
809 tip20::{TIP20Token, slots as tip20_slots},
810 };
811 use tempo_primitives::{
812 Block, TempoHeader, TempoPrimitives, TempoTxEnvelope, TempoTxType,
813 transaction::{
814 TempoTransaction,
815 envelope::TEMPO_SYSTEM_TX_SIGNATURE,
816 tempo_transaction::Call,
817 tt_signature::{PrimitiveSignature, TempoSignature},
818 tt_signed::AASigned,
819 },
820 };
821
822 const TEST_VALIDITY_WINDOW: u64 = 25;
824
825 struct CountingDatabaseRef {
826 address: Address,
827 code_hash: B256,
828 account: revm::state::AccountInfo,
829 bytecode: revm::bytecode::Bytecode,
830 account_reads: Arc<AtomicUsize>,
831 bytecode_reads: Arc<AtomicUsize>,
832 }
833
834 impl DatabaseRef for CountingDatabaseRef {
835 type Error = ProviderError;
836
837 fn basic_ref(
838 &self,
839 address: Address,
840 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
841 self.account_reads.fetch_add(1, Ordering::Relaxed);
842 Ok((address == self.address).then(|| self.account.clone()))
843 }
844
845 fn code_by_hash_ref(
846 &self,
847 code_hash: B256,
848 ) -> Result<revm::bytecode::Bytecode, Self::Error> {
849 self.bytecode_reads.fetch_add(1, Ordering::Relaxed);
850 Ok(if code_hash == self.code_hash {
851 self.bytecode.clone()
852 } else {
853 Default::default()
854 })
855 }
856
857 fn storage_ref(&self, _address: Address, _index: U256) -> Result<U256, Self::Error> {
858 Ok(U256::ZERO)
859 }
860
861 fn block_hash_ref(&self, _number: u64) -> Result<B256, Self::Error> {
862 Ok(B256::ZERO)
863 }
864 }
865
866 #[test]
867 fn cached_account_info_reader_uses_native_cached_reads() {
868 let address = Address::random();
869 let code_hash = B256::random();
870 let account = Account {
871 nonce: 7,
872 balance: U256::from(42),
873 bytecode_hash: Some(code_hash),
874 };
875 let bytecode = revm::bytecode::Bytecode::default();
876 let account_reads = Arc::new(AtomicUsize::new(0));
877 let bytecode_reads = Arc::new(AtomicUsize::new(0));
878 let provider = CountingDatabaseRef {
879 address,
880 code_hash,
881 account: revm::state::AccountInfo::new(
882 account.balance,
883 account.nonce,
884 code_hash,
885 bytecode.clone(),
886 ),
887 bytecode: bytecode.clone(),
888 account_reads: account_reads.clone(),
889 bytecode_reads: bytecode_reads.clone(),
890 };
891 let mut cached_reads = CachedReads::default();
892 let cached = CachedAccountInfoReader::new(cached_reads.as_db(provider));
893
894 assert_eq!(cached.basic_account(&address).unwrap(), Some(account));
895 assert_eq!(cached.basic_account(&address).unwrap(), Some(account));
896 assert_eq!(account_reads.load(Ordering::Relaxed), 1);
897
898 assert_eq!(
899 cached.bytecode_by_hash(&code_hash).unwrap(),
900 Some(Bytecode(bytecode.clone()))
901 );
902 assert_eq!(
903 cached.bytecode_by_hash(&code_hash).unwrap(),
904 Some(Bytecode(bytecode))
905 );
906 assert_eq!(bytecode_reads.load(Ordering::Relaxed), 1);
907 }
908
909 fn create_mock_block(timestamp: u64) -> SealedBlock<Block> {
911 let header = TempoHeader {
912 inner: Header {
913 timestamp,
914 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
915 excess_blob_gas: Some(0),
916 base_fee_per_gas: Some(TEMPO_T0_BASE_FEE),
917 ..Default::default()
918 },
919 ..Default::default()
920 };
921 let block = Block {
922 header,
923 body: Default::default(),
924 };
925 SealedBlock::seal_slow(block)
926 }
927
928 fn create_aa_transaction(
931 valid_after: Option<u64>,
932 valid_before: Option<u64>,
933 ) -> TempoPooledTransaction {
934 let mut builder = TxBuilder::aa(Address::random())
935 .fee_token(address!("0000000000000000000000000000000000000002"));
936 if let Some(va) = valid_after {
937 builder = builder.valid_after(va);
938 }
939 if let Some(vb) = valid_before {
940 builder = builder.valid_before(vb);
941 }
942 builder.build()
943 }
944
945 fn setup_validator(
947 transaction: &TempoPooledTransaction,
948 tip_timestamp: u64,
949 ) -> TempoTransactionValidator<MockEthProvider<TempoPrimitives, TempoChainSpec>> {
950 let provider = MockEthProvider::<TempoPrimitives>::new()
951 .with_chain_spec(Arc::unwrap_or_clone(MODERATO.clone()));
952 provider.add_account(
953 transaction.sender(),
954 ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
955 );
956 let block_with_gas = Block {
957 header: TempoHeader {
958 inner: Header {
959 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
960 ..Default::default()
961 },
962 ..Default::default()
963 },
964 ..Default::default()
965 };
966 provider.add_block(B256::random(), block_with_gas);
967
968 let usd_currency_value =
971 uint!(0x5553440000000000000000000000000000000000000000000000000000000006_U256);
972 let transfer_policy_id_packed =
975 uint!(0x0000000000000000000000010000000000000000000000000000000000000000_U256);
976 let balance_slot = TIP20Token::from_address(PATH_USD_ADDRESS)
978 .expect("PATH_USD_ADDRESS is a valid TIP20 token")
979 .balances[transaction.sender()]
980 .slot();
981 let fee_payer_balance = U256::from(1_000_000_000_000u64); provider.add_account(
984 PATH_USD_ADDRESS,
985 ExtendedAccount::new(0, U256::ZERO).extend_storage([
986 (tip20_slots::CURRENCY.into(), usd_currency_value),
987 (
988 tip20_slots::TRANSFER_POLICY_ID.into(),
989 transfer_policy_id_packed,
990 ),
991 (balance_slot.into(), fee_payer_balance),
992 ]),
993 );
994
995 let inner =
996 EthTransactionValidatorBuilder::new(provider.clone(), TempoEvmConfig::moderato())
997 .with_custom_tx_type(TempoTxType::AA as u8)
998 .disable_balance_check()
999 .build(InMemoryBlobStore::default());
1000 let amm_cache =
1001 AmmLiquidityCache::new(provider).expect("failed to setup AmmLiquidityCache");
1002 let validator = TempoTransactionValidator::new(
1003 inner,
1004 DEFAULT_AA_VALID_AFTER_MAX_SECS,
1005 DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
1006 amm_cache,
1007 );
1008
1009 let mock_block = create_mock_block(tip_timestamp);
1011 validator.on_new_head_block(&mock_block);
1012
1013 validator
1014 }
1015
1016 #[test]
1017 fn state_cache_for_tip_reuses_only_matching_tip_cache() {
1018 let tx = TxBuilder::eip1559(Address::random()).build_eip1559();
1019 let validator = setup_validator(&tx, 1);
1020 let (shared_tip_hash, shared_cache) = validator.cached_state.read().clone();
1021
1022 let matching_cache = validator.state_cache_for_tip(shared_tip_hash);
1023 assert!(Arc::ptr_eq(&matching_cache, &shared_cache));
1024
1025 let mismatched_tip_hash = if shared_tip_hash == B256::repeat_byte(0x42) {
1026 B256::repeat_byte(0x43)
1027 } else {
1028 B256::repeat_byte(0x42)
1029 };
1030 let ephemeral_cache = validator.state_cache_for_tip(mismatched_tip_hash);
1031 assert!(!Arc::ptr_eq(&ephemeral_cache, &shared_cache));
1032 }
1033
1034 #[test]
1035 fn latest_state_provider_uses_ephemeral_cache_when_tip_hash_mismatches_latest() {
1036 let tx = TxBuilder::eip1559(Address::random()).build_eip1559();
1037 let validator = setup_validator(&tx, 1);
1038 let latest_hash = validator.client().chain_info().unwrap().best_hash;
1039 let mismatched_tip_hash = if latest_hash == B256::repeat_byte(0x42) {
1040 B256::repeat_byte(0x43)
1041 } else {
1042 B256::repeat_byte(0x42)
1043 };
1044 let shared_cache = Arc::new(StateCache::default());
1045 *validator.cached_state.write() = (mismatched_tip_hash, shared_cache.clone());
1046
1047 let (_, validation_cache) = validator.latest_state_provider_and_cache().unwrap();
1048
1049 assert!(!Arc::ptr_eq(&validation_cache, &shared_cache));
1050 }
1051
1052 #[tokio::test]
1053 async fn test_aa_authorization_list_authorities_tracked() {
1054 use alloy_eips::eip7702::Authorization;
1055 use alloy_signer::SignerSync;
1056 use alloy_signer_local::PrivateKeySigner;
1057 use tempo_primitives::transaction::{
1058 TempoSignedAuthorization,
1059 tt_signature::{PrimitiveSignature, TempoSignature},
1060 };
1061
1062 let current_time = std::time::SystemTime::now()
1063 .duration_since(std::time::UNIX_EPOCH)
1064 .unwrap()
1065 .as_secs();
1066
1067 let authority_signer = PrivateKeySigner::random();
1068 let expected_authority = authority_signer.address();
1069 let authorization = Authorization {
1070 chain_id: U256::from(1),
1071 nonce: 0,
1072 address: Address::random(),
1073 };
1074 let signature = authority_signer
1075 .sign_hash_sync(&authorization.signature_hash())
1076 .expect("authorization signing should succeed");
1077 let tempo_authorization = TempoSignedAuthorization::new_unchecked(
1078 authorization,
1079 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature)),
1080 );
1081
1082 let transaction = TxBuilder::aa(Address::random())
1083 .fee_token(PATH_USD_ADDRESS)
1084 .authorization_list(vec![tempo_authorization])
1085 .build();
1086 let validator = setup_validator(&transaction, current_time);
1087
1088 let outcome = validator
1089 .validate_transaction(TransactionOrigin::External, transaction)
1090 .await;
1091
1092 match outcome {
1093 TransactionValidationOutcome::Valid { authorities, .. } => {
1094 let authorities = authorities.expect(
1095 "AA transactions with tempo_authorization_list should return authorities",
1096 );
1097 assert!(
1098 authorities.contains(&expected_authority),
1099 "AA authority recovered from tempo_authorization_list must be tracked"
1100 );
1101 }
1102 other => panic!("Expected Valid outcome with recovered authorities, got: {other:?}"),
1103 }
1104 }
1105
1106 #[tokio::test]
1107 async fn test_some_balance() {
1108 let transaction = TxBuilder::eip1559(Address::random())
1109 .value(U256::from(1))
1110 .build_eip1559();
1111 let validator = setup_validator(&transaction, 0);
1112
1113 let outcome = validator
1114 .validate_transaction(TransactionOrigin::External, transaction.clone())
1115 .await;
1116
1117 match outcome {
1118 TransactionValidationOutcome::Invalid(_, ref err) => {
1119 assert!(matches!(
1120 err.downcast_other_ref::<TempoPoolTransactionError>(),
1121 Some(TempoPoolTransactionError::Evm(
1122 TempoInvalidTransaction::ValueTransferNotAllowed
1123 ))
1124 ));
1125 }
1126 _ => panic!("Expected Invalid outcome with Evm error, got: {outcome:?}"),
1127 }
1128 }
1129
1130 #[tokio::test]
1131 async fn test_system_tx_rejected_as_invalid() {
1132 let tx = TxLegacy {
1133 chain_id: Some(MODERATO.chain_id()),
1134 nonce: 0,
1135 gas_price: 0,
1136 gas_limit: 0,
1137 to: TxKind::Call(Address::ZERO),
1138 value: U256::ZERO,
1139 input: Default::default(),
1140 };
1141 let envelope = TempoTxEnvelope::Legacy(Signed::new_unhashed(tx, TEMPO_SYSTEM_TX_SIGNATURE));
1142 let transaction = TempoPooledTransaction::new(
1143 reth_primitives_traits::Recovered::new_unchecked(envelope, Address::ZERO),
1144 );
1145 let validator = setup_validator(&transaction, 0);
1146
1147 let outcome = validator
1148 .validate_transaction(TransactionOrigin::External, transaction)
1149 .await;
1150
1151 match outcome {
1152 TransactionValidationOutcome::Invalid(_, err) => {
1153 assert!(matches!(
1154 err,
1155 InvalidPoolTransactionError::Consensus(
1156 InvalidTransactionError::TxTypeNotSupported
1157 )
1158 ));
1159 }
1160 _ => panic!("Expected Invalid outcome with TxTypeNotSupported error, got: {outcome:?}"),
1161 }
1162 }
1163
1164 #[tokio::test]
1165 async fn test_invalid_fee_payer_signature_rejected() {
1166 let calls: Vec<Call> = vec![Call {
1167 to: TxKind::Call(Address::random()),
1168 value: U256::ZERO,
1169 input: Default::default(),
1170 }];
1171
1172 let tx = TempoTransaction {
1173 chain_id: MODERATO.chain_id(),
1174 max_priority_fee_per_gas: 1_000_000_000,
1175 max_fee_per_gas: 20_000_000_000,
1176 gas_limit: 1_000_000,
1177 calls,
1178 nonce_key: U256::ZERO,
1179 nonce: 0,
1180 fee_token: Some(PATH_USD_ADDRESS),
1181 fee_payer_signature: Some(Signature::new(U256::ZERO, U256::ZERO, false)),
1182 ..Default::default()
1183 };
1184
1185 let signed = AASigned::new_unhashed(
1186 tx,
1187 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())),
1188 );
1189 let transaction = TempoPooledTransaction::new(
1190 TempoTxEnvelope::from(signed).try_into_recovered().unwrap(),
1191 );
1192 let validator = setup_validator(&transaction, 0);
1193
1194 let outcome = validator
1195 .validate_transaction(TransactionOrigin::External, transaction)
1196 .await;
1197
1198 match outcome {
1199 TransactionValidationOutcome::Invalid(_, ref err) => {
1200 assert!(matches!(
1201 err.downcast_other_ref::<TempoPoolTransactionError>(),
1202 Some(TempoPoolTransactionError::Evm(
1203 TempoInvalidTransaction::InvalidFeePayerSignature
1204 ))
1205 ));
1206 }
1207 _ => panic!("Expected Invalid outcome with Evm error, got: {outcome:?}"),
1208 }
1209 }
1210
1211 #[tokio::test]
1212 async fn test_self_sponsored_fee_payer_rejected() {
1213 use alloy_signer::SignerSync;
1214 use alloy_signer_local::PrivateKeySigner;
1215
1216 let signer = PrivateKeySigner::random();
1217 let sender = signer.address();
1218
1219 let mut tx = TempoTransaction {
1220 chain_id: MODERATO.chain_id(),
1221 max_priority_fee_per_gas: 1_000_000_000,
1222 max_fee_per_gas: 20_000_000_000,
1223 gas_limit: 1_000_000,
1224 calls: vec![Call {
1225 to: TxKind::Call(Address::random()),
1226 value: U256::ZERO,
1227 input: Default::default(),
1228 }],
1229 nonce_key: U256::ZERO,
1230 nonce: 0,
1231 fee_token: Some(PATH_USD_ADDRESS),
1232 fee_payer_signature: Some(Signature::new(U256::ZERO, U256::ZERO, false)),
1233 ..Default::default()
1234 };
1235
1236 let fee_payer_hash = tx.fee_payer_signature_hash(sender);
1237 tx.fee_payer_signature = Some(
1238 signer
1239 .sign_hash_sync(&fee_payer_hash)
1240 .expect("fee payer signing should succeed"),
1241 );
1242
1243 let signed = AASigned::new_unhashed(
1244 tx,
1245 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())),
1246 );
1247
1248 let envelope: TempoTxEnvelope = signed.into();
1249 let transaction = TempoPooledTransaction::new(
1250 reth_primitives_traits::Recovered::new_unchecked(envelope, sender),
1251 );
1252 let validator = setup_validator(&transaction, u64::MAX);
1253
1254 let outcome = validator
1255 .validate_transaction(TransactionOrigin::External, transaction)
1256 .await;
1257
1258 match outcome {
1259 TransactionValidationOutcome::Invalid(_, ref err) => {
1260 assert!(matches!(
1261 err.downcast_other_ref::<TempoPoolTransactionError>(),
1262 Some(TempoPoolTransactionError::Evm(
1263 TempoInvalidTransaction::SelfSponsoredFeePayer
1264 ))
1265 ));
1266 }
1267 _ => panic!("Expected Invalid outcome with Evm error, got: {outcome:?}"),
1268 }
1269 }
1270
1271 #[tokio::test]
1272 async fn test_aa_valid_before_check() {
1273 let current_time = std::time::SystemTime::now()
1275 .duration_since(std::time::UNIX_EPOCH)
1276 .unwrap()
1277 .as_secs();
1278
1279 let tx_no_valid_before = create_aa_transaction(None, None);
1281 let validator = setup_validator(&tx_no_valid_before, current_time);
1282 let outcome = validator
1283 .validate_transaction(TransactionOrigin::External, tx_no_valid_before)
1284 .await;
1285
1286 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1287 assert!(!matches!(
1288 err.downcast_other_ref::<TempoPoolTransactionError>(),
1289 Some(TempoPoolTransactionError::InvalidValidBefore { .. })
1290 ));
1291 }
1292
1293 let tx_too_close =
1295 create_aa_transaction(None, Some(current_time + AA_VALID_BEFORE_MIN_SECS));
1296 let validator = setup_validator(&tx_too_close, current_time);
1297 let outcome = validator
1298 .validate_transaction(TransactionOrigin::External, tx_too_close)
1299 .await;
1300
1301 match outcome {
1302 TransactionValidationOutcome::Invalid(_, ref err) => {
1303 assert!(matches!(
1304 err.downcast_other_ref::<TempoPoolTransactionError>(),
1305 Some(TempoPoolTransactionError::InvalidValidBefore { .. })
1306 ));
1307 }
1308 _ => panic!("Expected Invalid outcome with InvalidValidBefore error, got: {outcome:?}"),
1309 }
1310
1311 let tx_valid =
1313 create_aa_transaction(None, Some(current_time + AA_VALID_BEFORE_MIN_SECS + 1));
1314 let validator = setup_validator(&tx_valid, current_time);
1315 let outcome = validator
1316 .validate_transaction(TransactionOrigin::External, tx_valid)
1317 .await;
1318
1319 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1320 assert!(!matches!(
1321 err.downcast_other_ref::<TempoPoolTransactionError>(),
1322 Some(TempoPoolTransactionError::InvalidValidBefore { .. })
1323 ));
1324 }
1325 }
1326
1327 #[tokio::test]
1328 async fn test_aa_valid_after_check() {
1329 let current_time = std::time::SystemTime::now()
1331 .duration_since(std::time::UNIX_EPOCH)
1332 .unwrap()
1333 .as_secs();
1334
1335 let tx_no_valid_after = create_aa_transaction(None, None);
1337 let validator = setup_validator(&tx_no_valid_after, current_time);
1338 let outcome = validator
1339 .validate_transaction(TransactionOrigin::External, tx_no_valid_after)
1340 .await;
1341
1342 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1343 assert!(!matches!(
1344 err.downcast_other_ref::<TempoPoolTransactionError>(),
1345 Some(TempoPoolTransactionError::InvalidValidAfter { .. })
1346 ));
1347 }
1348
1349 let tx_within_limit = create_aa_transaction(Some(current_time + 60), None);
1351 let validator = setup_validator(&tx_within_limit, current_time);
1352 let outcome = validator
1353 .validate_transaction(TransactionOrigin::External, tx_within_limit)
1354 .await;
1355
1356 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1357 assert!(!matches!(
1358 err.downcast_other_ref::<TempoPoolTransactionError>(),
1359 Some(TempoPoolTransactionError::InvalidValidAfter { .. })
1360 ));
1361 }
1362
1363 let tx_too_far = create_aa_transaction(Some(current_time + 300), None);
1365 let validator = setup_validator(&tx_too_far, current_time);
1366 let outcome = validator
1367 .validate_transaction(TransactionOrigin::External, tx_too_far)
1368 .await;
1369
1370 match outcome {
1371 TransactionValidationOutcome::Invalid(_, ref err) => {
1372 assert!(matches!(
1373 err.downcast_other_ref::<TempoPoolTransactionError>(),
1374 Some(TempoPoolTransactionError::InvalidValidAfter { .. })
1375 ));
1376 }
1377 _ => panic!("Expected Invalid outcome with InvalidValidAfter error, got: {outcome:?}"),
1378 }
1379 }
1380
1381 #[tokio::test]
1384 async fn test_aa_intrinsic_gas_validation() {
1385 use alloy_primitives::{Signature, TxKind, address};
1386 use tempo_primitives::transaction::{
1387 TempoTransaction,
1388 tempo_transaction::Call,
1389 tt_signature::{PrimitiveSignature, TempoSignature},
1390 tt_signed::AASigned,
1391 };
1392
1393 let current_time = std::time::SystemTime::now()
1394 .duration_since(std::time::UNIX_EPOCH)
1395 .unwrap()
1396 .as_secs();
1397
1398 let create_aa_tx = |gas_limit: u64| {
1400 let calls: Vec<Call> = (0..10)
1401 .map(|i| Call {
1402 to: TxKind::Call(Address::from([i as u8; 20])),
1403 value: U256::ZERO,
1404 input: alloy_primitives::Bytes::from(vec![0x00; 100]),
1405 })
1406 .collect();
1407
1408 let tx = TempoTransaction {
1409 chain_id: MODERATO.chain_id(),
1410 max_priority_fee_per_gas: 1_000_000_000,
1411 max_fee_per_gas: 20_000_000_000, gas_limit,
1413 calls,
1414 nonce_key: U256::ZERO,
1415 nonce: 0,
1416 fee_token: Some(address!("0000000000000000000000000000000000000002")),
1417 ..Default::default()
1418 };
1419
1420 let signed = AASigned::new_unhashed(
1421 tx,
1422 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(
1423 Signature::test_signature(),
1424 )),
1425 );
1426 TempoPooledTransaction::new(TempoTxEnvelope::from(signed).try_into_recovered().unwrap())
1427 };
1428
1429 let tx_low_gas = create_aa_tx(30_000);
1432 let validator = setup_validator(&tx_low_gas, current_time);
1433 let outcome = validator
1434 .validate_transaction(TransactionOrigin::External, tx_low_gas)
1435 .await;
1436
1437 match outcome {
1438 TransactionValidationOutcome::Invalid(_, ref err) => {
1439 assert!(matches!(
1440 err.downcast_other_ref::<TempoPoolTransactionError>(),
1441 Some(TempoPoolTransactionError::Evm(
1442 TempoInvalidTransaction::EthInvalidTransaction(
1443 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1444 )
1445 ))
1446 ));
1447 }
1448 _ => panic!(
1449 "Expected Invalid outcome with InsufficientGasForAAIntrinsicCost, got: {outcome:?}"
1450 ),
1451 }
1452
1453 let tx_high_gas = create_aa_tx(1_000_000);
1455 let validator = setup_validator(&tx_high_gas, current_time);
1456 let outcome = validator
1457 .validate_transaction(TransactionOrigin::External, tx_high_gas)
1458 .await;
1459
1460 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1461 assert!(!matches!(
1462 err.downcast_other_ref::<TempoPoolTransactionError>(),
1463 Some(TempoPoolTransactionError::Evm(
1464 TempoInvalidTransaction::EthInvalidTransaction(
1465 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1466 )
1467 ))
1468 ));
1469 }
1470 }
1471
1472 #[tokio::test]
1480 async fn test_aa_create_tx_with_2d_nonce_intrinsic_gas() {
1481 use alloy_primitives::Signature;
1482 use tempo_primitives::transaction::{
1483 TempoTransaction,
1484 tempo_transaction::Call as TxCall,
1485 tt_signature::{PrimitiveSignature, TempoSignature},
1486 tt_signed::AASigned,
1487 };
1488
1489 let current_time = std::time::SystemTime::now()
1490 .duration_since(std::time::UNIX_EPOCH)
1491 .unwrap()
1492 .as_secs();
1493
1494 let create_aa_tx = |gas_limit: u64, nonce_key: U256, is_create: bool| {
1496 let calls: Vec<TxCall> = if is_create {
1497 vec![TxCall {
1498 to: TxKind::Create,
1499 value: U256::ZERO,
1500 input: alloy_primitives::Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xF3]),
1501 }]
1502 } else {
1503 (0..10)
1504 .map(|i| TxCall {
1505 to: TxKind::Call(Address::from([i as u8; 20])),
1506 value: U256::ZERO,
1507 input: alloy_primitives::Bytes::from(vec![0x00; 100]),
1508 })
1509 .collect()
1510 };
1511
1512 let valid_before = if nonce_key == TEMPO_EXPIRING_NONCE_KEY {
1513 Some(core::num::NonZeroU64::new(current_time + TEST_VALIDITY_WINDOW).unwrap())
1514 } else {
1515 None
1516 };
1517
1518 let tx = TempoTransaction {
1519 chain_id: MODERATO.chain_id(),
1520 max_priority_fee_per_gas: 1_000_000_000,
1521 max_fee_per_gas: 20_000_000_000,
1522 gas_limit,
1523 calls,
1524 nonce_key,
1525 nonce: 0,
1526 valid_before,
1527 fee_token: Some(address!("0000000000000000000000000000000000000002")),
1528 ..Default::default()
1529 };
1530
1531 let signed = AASigned::new_unhashed(
1532 tx,
1533 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(
1534 Signature::test_signature(),
1535 )),
1536 );
1537 TempoPooledTransaction::new(TempoTxEnvelope::from(signed).try_into_recovered().unwrap())
1538 };
1539
1540 let tx_1d_low_gas = create_aa_tx(30_000, U256::ZERO, false);
1542 let validator1 = setup_validator(&tx_1d_low_gas, current_time);
1543 let outcome1 = validator1
1544 .validate_transaction(TransactionOrigin::External, tx_1d_low_gas)
1545 .await;
1546
1547 match outcome1 {
1548 TransactionValidationOutcome::Invalid(_, ref err) => {
1549 assert!(
1550 matches!(
1551 err.downcast_other_ref::<TempoPoolTransactionError>(),
1552 Some(TempoPoolTransactionError::Evm(
1553 TempoInvalidTransaction::EthInvalidTransaction(
1554 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1555 )
1556 ))
1557 ),
1558 "1D nonce with low gas should fail InsufficientGasForAAIntrinsicCost, got: {err:?}"
1559 );
1560 }
1561 _ => panic!("Expected Invalid outcome, got: {outcome1:?}"),
1562 }
1563
1564 let tx_2d_low_gas = create_aa_tx(30_000, TEMPO_EXPIRING_NONCE_KEY, false);
1567 let validator2 = setup_validator(&tx_2d_low_gas, current_time);
1568 let outcome2 = validator2
1569 .validate_transaction(TransactionOrigin::External, tx_2d_low_gas)
1570 .await;
1571
1572 match outcome2 {
1573 TransactionValidationOutcome::Invalid(_, ref err) => {
1574 assert!(
1575 matches!(
1576 err.downcast_other_ref::<TempoPoolTransactionError>(),
1577 Some(TempoPoolTransactionError::Evm(
1578 TempoInvalidTransaction::EthInvalidTransaction(
1579 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1580 )
1581 ))
1582 ),
1583 "2D nonce with low gas should fail InsufficientGasForAAIntrinsicCost, got: {err:?}"
1584 );
1585 }
1586 _ => panic!("Expected Invalid outcome, got: {outcome2:?}"),
1587 }
1588
1589 let tx_1d_high_gas = create_aa_tx(1_000_000, U256::ZERO, false);
1591 let validator3 = setup_validator(&tx_1d_high_gas, current_time);
1592 let outcome3 = validator3
1593 .validate_transaction(TransactionOrigin::External, tx_1d_high_gas)
1594 .await;
1595
1596 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome3 {
1598 assert!(
1599 !matches!(
1600 err.downcast_other_ref::<TempoPoolTransactionError>(),
1601 Some(TempoPoolTransactionError::Evm(
1602 TempoInvalidTransaction::EthInvalidTransaction(
1603 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1604 )
1605 ))
1606 ),
1607 "1D nonce with high gas should NOT fail InsufficientGasForAAIntrinsicCost, got: {err:?}"
1608 );
1609 }
1610
1611 let tx_2d_high_gas = create_aa_tx(1_000_000, TEMPO_EXPIRING_NONCE_KEY, false);
1613 let validator4 = setup_validator(&tx_2d_high_gas, current_time);
1614 let outcome4 = validator4
1615 .validate_transaction(TransactionOrigin::External, tx_2d_high_gas)
1616 .await;
1617
1618 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome4 {
1620 assert!(
1621 !matches!(
1622 err.downcast_other_ref::<TempoPoolTransactionError>(),
1623 Some(TempoPoolTransactionError::Evm(
1624 TempoInvalidTransaction::EthInvalidTransaction(
1625 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1626 )
1627 ))
1628 ),
1629 "2D nonce with high gas should NOT fail InsufficientGasForAAIntrinsicCost, got: {err:?}"
1630 );
1631 }
1632 }
1633
1634 #[tokio::test]
1635 async fn test_expiring_nonce_intrinsic_gas_uses_lower_cost() {
1636 use alloy_primitives::{Signature, TxKind, address};
1637 use tempo_primitives::transaction::{
1638 TempoTransaction,
1639 tempo_transaction::Call,
1640 tt_signature::{PrimitiveSignature, TempoSignature},
1641 tt_signed::AASigned,
1642 };
1643
1644 let current_time = std::time::SystemTime::now()
1645 .duration_since(std::time::UNIX_EPOCH)
1646 .unwrap()
1647 .as_secs();
1648
1649 let create_expiring_nonce_tx = |gas_limit: u64| {
1651 let calls: Vec<Call> = vec![Call {
1652 to: TxKind::Call(Address::from([1u8; 20])),
1653 value: U256::ZERO,
1654 input: alloy_primitives::Bytes::from(vec![0xd0, 0x9d, 0xe0, 0x8a]), }];
1656
1657 let tx = TempoTransaction {
1658 chain_id: 1,
1659 max_priority_fee_per_gas: 1_000_000_000,
1660 max_fee_per_gas: 20_000_000_000,
1661 gas_limit,
1662 calls,
1663 nonce_key: TEMPO_EXPIRING_NONCE_KEY, nonce: 0,
1665 valid_before: Some(core::num::NonZeroU64::new(current_time + 25).unwrap()), fee_token: Some(address!("0000000000000000000000000000000000000002")),
1667 ..Default::default()
1668 };
1669
1670 let signed = AASigned::new_unhashed(
1671 tx,
1672 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(
1673 Signature::test_signature(),
1674 )),
1675 );
1676 TempoPooledTransaction::new(TempoTxEnvelope::from(signed).try_into_recovered().unwrap())
1677 };
1678
1679 let tx = create_expiring_nonce_tx(50_000);
1683 let validator = setup_validator(&tx, current_time);
1684 let outcome = validator
1685 .validate_transaction(TransactionOrigin::External, tx)
1686 .await;
1687
1688 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1690 let is_intrinsic_gas_error = matches!(
1691 err.downcast_other_ref::<TempoPoolTransactionError>(),
1692 Some(TempoPoolTransactionError::Evm(
1693 TempoInvalidTransaction::EthInvalidTransaction(
1694 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1695 )
1696 ))
1697 ) || matches!(
1698 err.downcast_other_ref::<InvalidPoolTransactionError>(),
1699 Some(InvalidPoolTransactionError::IntrinsicGasTooLow)
1700 );
1701 assert!(
1702 !is_intrinsic_gas_error,
1703 "Expiring nonce tx with 50k gas should NOT fail intrinsic gas check, got: {err:?}"
1704 );
1705 }
1706 }
1707
1708 #[tokio::test]
1714 async fn test_existing_2d_nonce_key_intrinsic_gas() {
1715 use alloy_primitives::{Signature, TxKind, address};
1716 use tempo_primitives::transaction::{
1717 TempoTransaction,
1718 tempo_transaction::Call,
1719 tt_signature::{PrimitiveSignature, TempoSignature},
1720 tt_signed::AASigned,
1721 };
1722
1723 let current_time = std::time::SystemTime::now()
1724 .duration_since(std::time::UNIX_EPOCH)
1725 .unwrap()
1726 .as_secs();
1727
1728 let create_aa_tx = |gas_limit: u64, nonce_key: U256, nonce: u64| {
1730 let calls: Vec<Call> = vec![Call {
1731 to: TxKind::Call(Address::from([1u8; 20])),
1732 value: U256::ZERO,
1733 input: alloy_primitives::Bytes::from(vec![0xd0, 0x9d, 0xe0, 0x8a]), }];
1735
1736 let tx = TempoTransaction {
1737 chain_id: MODERATO.chain_id(),
1738 max_priority_fee_per_gas: 1_000_000_000,
1739 max_fee_per_gas: 20_000_000_000,
1740 gas_limit,
1741 calls,
1742 nonce_key,
1743 nonce,
1744 fee_token: Some(address!("0000000000000000000000000000000000000002")),
1745 ..Default::default()
1746 };
1747
1748 let signed = AASigned::new_unhashed(
1749 tx,
1750 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(
1751 Signature::test_signature(),
1752 )),
1753 );
1754 TempoPooledTransaction::new(TempoTxEnvelope::from(signed).try_into_recovered().unwrap())
1755 };
1756
1757 let tx_1d = create_aa_tx(50_000, U256::ZERO, 5);
1760 let validator = setup_validator(&tx_1d, current_time);
1761 let outcome = validator
1762 .validate_transaction(TransactionOrigin::External, tx_1d)
1763 .await;
1764
1765 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1766 let is_gas_error = matches!(
1767 err.downcast_other_ref::<TempoPoolTransactionError>(),
1768 Some(TempoPoolTransactionError::Evm(
1769 TempoInvalidTransaction::EthInvalidTransaction(
1770 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1771 )
1772 ))
1773 ) || matches!(
1774 err.downcast_other_ref::<InvalidPoolTransactionError>(),
1775 Some(InvalidPoolTransactionError::IntrinsicGasTooLow)
1776 );
1777 assert!(
1778 !is_gas_error,
1779 "1D nonce with nonce>0 and 50k gas should NOT fail intrinsic gas check, got: {err:?}"
1780 );
1781 }
1782
1783 let tx_2d_ok = create_aa_tx(50_000, U256::from(1), 5);
1786 let validator = setup_validator(&tx_2d_ok, current_time);
1787 let outcome = validator
1788 .validate_transaction(TransactionOrigin::External, tx_2d_ok)
1789 .await;
1790
1791 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1792 let is_gas_error = matches!(
1793 err.downcast_other_ref::<TempoPoolTransactionError>(),
1794 Some(TempoPoolTransactionError::Evm(
1795 TempoInvalidTransaction::EthInvalidTransaction(
1796 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1797 )
1798 ))
1799 ) || matches!(
1800 err.downcast_other_ref::<InvalidPoolTransactionError>(),
1801 Some(InvalidPoolTransactionError::IntrinsicGasTooLow)
1802 );
1803 assert!(
1804 !is_gas_error,
1805 "Existing 2D nonce key with 50k gas should NOT fail intrinsic gas check, got: {err:?}"
1806 );
1807 }
1808
1809 let tx_2d_low = create_aa_tx(22_000, U256::from(1), 5);
1813 let validator = setup_validator(&tx_2d_low, current_time);
1814 let outcome = validator
1815 .validate_transaction(TransactionOrigin::External, tx_2d_low)
1816 .await;
1817
1818 match outcome {
1819 TransactionValidationOutcome::Invalid(_, ref err) => {
1820 let is_gas_error = matches!(
1821 err.downcast_other_ref::<TempoPoolTransactionError>(),
1822 Some(TempoPoolTransactionError::Evm(
1823 TempoInvalidTransaction::EthInvalidTransaction(
1824 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1825 )
1826 ))
1827 ) || matches!(
1828 err.downcast_other_ref::<InvalidPoolTransactionError>(),
1829 Some(InvalidPoolTransactionError::IntrinsicGasTooLow)
1830 );
1831 assert!(
1832 is_gas_error,
1833 "Existing 2D nonce key with 22k gas should fail intrinsic gas check, got: {err:?}"
1834 );
1835 }
1836 _ => panic!(
1837 "Expected Invalid outcome for existing 2D nonce with insufficient gas, got: {outcome:?}"
1838 ),
1839 }
1840
1841 let tx_1d_low = create_aa_tx(22_000, U256::ZERO, 5);
1844 let validator = setup_validator(&tx_1d_low, current_time);
1845 let outcome = validator
1846 .validate_transaction(TransactionOrigin::External, tx_1d_low)
1847 .await;
1848
1849 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1850 let is_gas_error = matches!(
1851 err.downcast_other_ref::<TempoPoolTransactionError>(),
1852 Some(TempoPoolTransactionError::Evm(
1853 TempoInvalidTransaction::EthInvalidTransaction(
1854 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
1855 )
1856 ))
1857 ) || matches!(
1858 err.downcast_other_ref::<InvalidPoolTransactionError>(),
1859 Some(InvalidPoolTransactionError::IntrinsicGasTooLow)
1860 );
1861 assert!(
1862 !is_gas_error,
1863 "1D nonce with nonce>0 and 22k gas should NOT fail intrinsic gas check, got: {err:?}"
1864 );
1865 }
1866 }
1867
1868 #[tokio::test]
1869 async fn test_non_zero_value_in_eip1559_rejected() {
1870 let transaction = TxBuilder::eip1559(Address::random())
1871 .value(U256::from(1))
1872 .build_eip1559();
1873
1874 let current_time = std::time::SystemTime::now()
1875 .duration_since(std::time::UNIX_EPOCH)
1876 .unwrap()
1877 .as_secs();
1878 let validator = setup_validator(&transaction, current_time);
1879
1880 let outcome = validator
1881 .validate_transaction(TransactionOrigin::External, transaction)
1882 .await;
1883
1884 match outcome {
1885 TransactionValidationOutcome::Invalid(_, ref err) => {
1886 assert!(matches!(
1887 err.downcast_other_ref::<TempoPoolTransactionError>(),
1888 Some(TempoPoolTransactionError::Evm(
1889 TempoInvalidTransaction::ValueTransferNotAllowed
1890 ))
1891 ));
1892 }
1893 _ => panic!("Expected Invalid outcome with Evm error, got: {outcome:?}"),
1894 }
1895 }
1896
1897 #[tokio::test]
1898 async fn test_zero_value_passes_value_check() {
1899 let transaction = TxBuilder::eip1559(Address::random()).build_eip1559();
1901 assert!(transaction.value().is_zero(), "Test expects zero-value tx");
1902
1903 let current_time = std::time::SystemTime::now()
1904 .duration_since(std::time::UNIX_EPOCH)
1905 .unwrap()
1906 .as_secs();
1907 let validator = setup_validator(&transaction, current_time);
1908
1909 let outcome = validator
1910 .validate_transaction(TransactionOrigin::External, transaction)
1911 .await;
1912
1913 assert!(
1914 matches!(outcome, TransactionValidationOutcome::Valid { .. }),
1915 "Zero-value tx should pass validation, got: {outcome:?}"
1916 );
1917 }
1918
1919 #[tokio::test]
1920 async fn test_invalid_fee_token_rejected() {
1921 let invalid_fee_token = address!("1234567890123456789012345678901234567890");
1922
1923 let transaction = TxBuilder::aa(Address::random())
1924 .fee_token(invalid_fee_token)
1925 .build();
1926
1927 let current_time = std::time::SystemTime::now()
1928 .duration_since(std::time::UNIX_EPOCH)
1929 .unwrap()
1930 .as_secs();
1931 let validator = setup_validator(&transaction, current_time);
1932
1933 let outcome = validator
1934 .validate_transaction(TransactionOrigin::External, transaction)
1935 .await;
1936
1937 match outcome {
1938 TransactionValidationOutcome::Invalid(_, ref err) => {
1939 assert!(matches!(
1940 err.downcast_other_ref::<TempoPoolTransactionError>(),
1941 Some(TempoPoolTransactionError::Evm(
1942 TempoInvalidTransaction::FeeTokenNotTip20 { .. }
1943 ))
1944 ));
1945 }
1946 _ => panic!("Expected Invalid outcome with Evm error, got: {outcome:?}"),
1947 }
1948 }
1949
1950 #[tokio::test]
1951 async fn test_aa_valid_after_and_valid_before_both_valid() {
1952 let current_time = std::time::SystemTime::now()
1953 .duration_since(std::time::UNIX_EPOCH)
1954 .unwrap()
1955 .as_secs();
1956
1957 let valid_after = current_time + 60;
1958 let valid_before = current_time + 3600;
1959
1960 let transaction = create_aa_transaction(Some(valid_after), Some(valid_before));
1961 let validator = setup_validator(&transaction, current_time);
1962
1963 let outcome = validator
1964 .validate_transaction(TransactionOrigin::External, transaction)
1965 .await;
1966
1967 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
1968 let tempo_err = err.downcast_other_ref::<TempoPoolTransactionError>();
1969 assert!(
1970 !matches!(
1971 tempo_err,
1972 Some(TempoPoolTransactionError::InvalidValidAfter { .. })
1973 | Some(TempoPoolTransactionError::InvalidValidBefore { .. })
1974 ),
1975 "Should not fail with validity window errors"
1976 );
1977 }
1978 }
1979
1980 #[tokio::test]
1981 async fn test_fee_cap_below_min_base_fee_rejected() {
1982 let current_time = std::time::SystemTime::now()
1983 .duration_since(std::time::UNIX_EPOCH)
1984 .unwrap()
1985 .as_secs();
1986
1987 let transaction = TxBuilder::aa(Address::random())
1990 .max_fee(1_000_000_000) .max_priority_fee(1_000_000_000)
1992 .build();
1993
1994 let validator = setup_validator(&transaction, current_time);
1995
1996 let outcome = validator
1997 .validate_transaction(TransactionOrigin::External, transaction)
1998 .await;
1999
2000 match outcome {
2001 TransactionValidationOutcome::Invalid(_, ref err) => {
2002 assert!(
2003 matches!(
2004 err.downcast_other_ref::<TempoPoolTransactionError>(),
2005 Some(TempoPoolTransactionError::Evm(
2006 TempoInvalidTransaction::EthInvalidTransaction(
2007 InvalidTransaction::GasPriceLessThanBasefee
2008 )
2009 ))
2010 ),
2011 "Expected Evm error, got: {err:?}"
2012 );
2013 }
2014 _ => panic!("Expected Invalid outcome with Evm error, got: {outcome:?}"),
2015 }
2016 }
2017
2018 #[tokio::test]
2019 async fn test_fee_cap_at_min_base_fee_passes() {
2020 let current_time = std::time::SystemTime::now()
2021 .duration_since(std::time::UNIX_EPOCH)
2022 .unwrap()
2023 .as_secs();
2024
2025 let transaction = TxBuilder::aa(Address::random())
2027 .max_fee(u128::from(TEMPO_T1_BASE_FEE))
2028 .max_priority_fee(1_000_000_000)
2029 .build();
2030
2031 let validator = setup_validator(&transaction, current_time);
2032
2033 let outcome = validator
2034 .validate_transaction(TransactionOrigin::External, transaction)
2035 .await;
2036
2037 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
2039 assert!(
2040 !matches!(
2041 err.downcast_other_ref::<TempoPoolTransactionError>(),
2042 Some(TempoPoolTransactionError::Evm(
2043 TempoInvalidTransaction::EthInvalidTransaction(
2044 InvalidTransaction::GasPriceLessThanBasefee
2045 )
2046 ))
2047 ),
2048 "Should not fail with FeeCapBelowMinBaseFee when fee cap equals min base fee"
2049 );
2050 }
2051 }
2052
2053 #[tokio::test]
2054 async fn test_fee_cap_above_min_base_fee_passes() {
2055 let current_time = std::time::SystemTime::now()
2056 .duration_since(std::time::UNIX_EPOCH)
2057 .unwrap()
2058 .as_secs();
2059
2060 let transaction = TxBuilder::aa(Address::random())
2063 .max_fee(20_000_000_000) .max_priority_fee(1_000_000_000)
2065 .build();
2066
2067 let validator = setup_validator(&transaction, current_time);
2068
2069 let outcome = validator
2070 .validate_transaction(TransactionOrigin::External, transaction)
2071 .await;
2072
2073 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
2075 assert!(
2076 !matches!(
2077 err.downcast_other_ref::<TempoPoolTransactionError>(),
2078 Some(TempoPoolTransactionError::Evm(
2079 TempoInvalidTransaction::EthInvalidTransaction(
2080 InvalidTransaction::GasPriceLessThanBasefee
2081 )
2082 ))
2083 ),
2084 "Should not fail with FeeCapBelowMinBaseFee when fee cap is above min base fee"
2085 );
2086 }
2087 }
2088
2089 #[tokio::test]
2090 async fn test_eip1559_fee_cap_below_min_base_fee_rejected() {
2091 let current_time = std::time::SystemTime::now()
2092 .duration_since(std::time::UNIX_EPOCH)
2093 .unwrap()
2094 .as_secs();
2095
2096 let transaction = TxBuilder::eip1559(Address::random())
2098 .max_fee(1_000_000_000) .max_priority_fee(1_000_000_000)
2100 .build_eip1559();
2101
2102 let validator = setup_validator(&transaction, current_time);
2103
2104 let outcome = validator
2105 .validate_transaction(TransactionOrigin::External, transaction)
2106 .await;
2107
2108 match outcome {
2109 TransactionValidationOutcome::Invalid(_, ref err) => {
2110 assert!(
2111 matches!(
2112 err.downcast_other_ref::<TempoPoolTransactionError>(),
2113 Some(TempoPoolTransactionError::Evm(
2114 TempoInvalidTransaction::EthInvalidTransaction(
2115 InvalidTransaction::GasPriceLessThanBasefee
2116 )
2117 ))
2118 ),
2119 "Expected Evm error for EIP-1559 tx, got: {err:?}"
2120 );
2121 }
2122 _ => panic!("Expected Invalid outcome with Evm error, got: {outcome:?}"),
2123 }
2124 }
2125
2126 mod keychain_validation {
2127 use super::*;
2128 use reth_transaction_pool::error::PoolTransactionError;
2129
2130 #[test]
2131 fn test_legacy_keychain_post_t1c_is_bad_transaction() {
2132 assert!(
2133 TempoPoolTransactionError::Evm(TempoInvalidTransaction::LegacyKeychainSignature)
2134 .is_bad_transaction(),
2135 "Post-T1C V1 rejection should be a bad transaction (permanent)"
2136 );
2137 }
2138
2139 #[test]
2140 fn test_v2_keychain_pre_t1c_is_not_bad_transaction() {
2141 assert!(
2142 !TempoPoolTransactionError::Evm(
2143 TempoInvalidTransaction::V2KeychainBeforeActivation
2144 )
2145 .is_bad_transaction(),
2146 "Pre-T1C V2 rejection should NOT be a bad transaction (transient)"
2147 );
2148 }
2149
2150 #[test]
2151 fn test_expired_access_key_is_not_bad_transaction() {
2152 assert!(
2153 !TempoPoolTransactionError::AccessKeyExpired {
2154 expiry: 1,
2155 min_allowed: 4,
2156 }
2157 .is_bad_transaction(),
2158 "Expired access key rejection should NOT be a bad transaction (timing-sensitive)"
2159 );
2160 }
2161
2162 #[test]
2163 fn test_expired_key_authorization_is_not_bad_transaction() {
2164 assert!(
2165 !TempoPoolTransactionError::KeyAuthorizationExpired {
2166 expiry: 1,
2167 min_allowed: 4,
2168 }
2169 .is_bad_transaction(),
2170 "Expired key authorization rejection should NOT be a bad transaction (timing-sensitive)"
2171 );
2172 }
2173 }
2174
2175 fn create_aa_transaction_with_authorizations(
2181 authorization_count: usize,
2182 ) -> TempoPooledTransaction {
2183 use alloy_eips::eip7702::Authorization;
2184 use alloy_primitives::{Signature, TxKind, address};
2185 use tempo_primitives::transaction::{
2186 TempoSignedAuthorization, TempoTransaction,
2187 tempo_transaction::Call,
2188 tt_signature::{PrimitiveSignature, TempoSignature},
2189 tt_signed::AASigned,
2190 };
2191
2192 let authorizations: Vec<TempoSignedAuthorization> = (0..authorization_count)
2194 .map(|i| {
2195 let auth = Authorization {
2196 chain_id: U256::from(1),
2197 nonce: i as u64,
2198 address: address!("0000000000000000000000000000000000000001"),
2199 };
2200 TempoSignedAuthorization::new_unchecked(
2201 auth,
2202 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(
2203 Signature::test_signature(),
2204 )),
2205 )
2206 })
2207 .collect();
2208
2209 let tx_aa = TempoTransaction {
2210 chain_id: 1,
2211 max_priority_fee_per_gas: 1_000_000_000,
2212 max_fee_per_gas: 20_000_000_000, gas_limit: 1_000_000,
2214 calls: vec![Call {
2215 to: TxKind::Call(address!("0000000000000000000000000000000000000001")),
2216 value: U256::ZERO,
2217 input: alloy_primitives::Bytes::new(),
2218 }],
2219 nonce_key: U256::ZERO,
2220 nonce: 0,
2221 fee_token: Some(address!("0000000000000000000000000000000000000002")),
2222 fee_payer_signature: None,
2223 valid_after: None,
2224 valid_before: None,
2225 access_list: Default::default(),
2226 tempo_authorization_list: authorizations,
2227 key_authorization: None,
2228 };
2229
2230 let signed_tx = AASigned::new_unhashed(
2231 tx_aa,
2232 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())),
2233 );
2234 let envelope: TempoTxEnvelope = signed_tx.into();
2235 let recovered = envelope.try_into_recovered().unwrap();
2236 TempoPooledTransaction::new(recovered)
2237 }
2238
2239 #[tokio::test]
2240 async fn test_aa_too_many_authorizations_rejected() {
2241 let current_time = std::time::SystemTime::now()
2242 .duration_since(std::time::UNIX_EPOCH)
2243 .unwrap()
2244 .as_secs();
2245
2246 let transaction =
2248 create_aa_transaction_with_authorizations(DEFAULT_MAX_TEMPO_AUTHORIZATIONS + 1);
2249 let validator = setup_validator(&transaction, current_time);
2250
2251 let outcome = validator
2252 .validate_transaction(TransactionOrigin::External, transaction)
2253 .await;
2254
2255 match &outcome {
2256 TransactionValidationOutcome::Invalid(_, err) => {
2257 let error_msg = err.to_string();
2258 assert!(
2259 error_msg.contains("Too many authorizations"),
2260 "Expected TooManyAuthorizations error, got: {error_msg}"
2261 );
2262 }
2263 other => panic!("Expected Invalid outcome, got: {other:?}"),
2264 }
2265 }
2266
2267 #[tokio::test]
2268 async fn test_aa_authorization_count_at_limit_accepted() {
2269 let current_time = std::time::SystemTime::now()
2270 .duration_since(std::time::UNIX_EPOCH)
2271 .unwrap()
2272 .as_secs();
2273
2274 let transaction =
2276 create_aa_transaction_with_authorizations(DEFAULT_MAX_TEMPO_AUTHORIZATIONS);
2277 let validator = setup_validator(&transaction, current_time);
2278
2279 let outcome = validator
2280 .validate_transaction(TransactionOrigin::External, transaction)
2281 .await;
2282
2283 if let TransactionValidationOutcome::Invalid(_, err) = &outcome {
2285 let error_msg = err.to_string();
2286 assert!(
2287 !error_msg.contains("Too many authorizations"),
2288 "Should not fail with TooManyAuthorizations at the limit, got: {error_msg}"
2289 );
2290 }
2291 }
2292
2293 #[tokio::test]
2295 async fn test_aa_no_calls_rejected() {
2296 let current_time = std::time::SystemTime::now()
2297 .duration_since(std::time::UNIX_EPOCH)
2298 .unwrap()
2299 .as_secs();
2300
2301 let transaction = TxBuilder::aa(Address::random())
2303 .fee_token(address!("0000000000000000000000000000000000000002"))
2304 .calls(vec![]) .build();
2306 let validator = setup_validator(&transaction, current_time);
2307
2308 let outcome = validator
2309 .validate_transaction(TransactionOrigin::External, transaction)
2310 .await;
2311
2312 match outcome {
2313 TransactionValidationOutcome::Invalid(_, ref err) => {
2314 assert!(
2315 matches!(
2316 err.downcast_other_ref::<TempoPoolTransactionError>(),
2317 Some(TempoPoolTransactionError::Evm(
2318 TempoInvalidTransaction::CallsValidation(_)
2319 ))
2320 ),
2321 "Expected NoCalls error, got: {err:?}"
2322 );
2323 }
2324 _ => panic!("Expected Invalid outcome with NoCalls error, got: {outcome:?}"),
2325 }
2326 }
2327
2328 #[tokio::test]
2330 async fn test_aa_create_call_not_first_rejected() {
2331 let current_time = std::time::SystemTime::now()
2332 .duration_since(std::time::UNIX_EPOCH)
2333 .unwrap()
2334 .as_secs();
2335
2336 let calls = vec![
2338 Call {
2339 to: TxKind::Call(Address::random()), value: U256::ZERO,
2341 input: Default::default(),
2342 },
2343 Call {
2344 to: TxKind::Create, value: U256::ZERO,
2346 input: Default::default(),
2347 },
2348 ];
2349
2350 let transaction = TxBuilder::aa(Address::random())
2351 .fee_token(address!("0000000000000000000000000000000000000002"))
2352 .calls(calls)
2353 .build();
2354 let validator = setup_validator(&transaction, current_time);
2355
2356 let outcome = validator
2357 .validate_transaction(TransactionOrigin::External, transaction)
2358 .await;
2359
2360 match outcome {
2361 TransactionValidationOutcome::Invalid(_, ref err) => {
2362 assert!(
2363 matches!(
2364 err.downcast_other_ref::<TempoPoolTransactionError>(),
2365 Some(TempoPoolTransactionError::Evm(
2366 TempoInvalidTransaction::CallsValidation(_)
2367 ))
2368 ),
2369 "Expected CreateCallNotFirst error, got: {err:?}"
2370 );
2371 }
2372 _ => panic!("Expected Invalid outcome with CreateCallNotFirst error, got: {outcome:?}"),
2373 }
2374 }
2375
2376 #[tokio::test]
2378 async fn test_aa_create_call_first_accepted() {
2379 let current_time = std::time::SystemTime::now()
2380 .duration_since(std::time::UNIX_EPOCH)
2381 .unwrap()
2382 .as_secs();
2383
2384 let calls = vec![
2386 Call {
2387 to: TxKind::Create, value: U256::ZERO,
2389 input: Default::default(),
2390 },
2391 Call {
2392 to: TxKind::Call(Address::random()), value: U256::ZERO,
2394 input: Default::default(),
2395 },
2396 ];
2397
2398 let transaction = TxBuilder::aa(Address::random())
2399 .fee_token(address!("0000000000000000000000000000000000000002"))
2400 .calls(calls)
2401 .build();
2402 let validator = setup_validator(&transaction, current_time);
2403
2404 let outcome = validator
2405 .validate_transaction(TransactionOrigin::External, transaction)
2406 .await;
2407
2408 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
2410 assert!(
2411 !matches!(
2412 err.downcast_other_ref::<TempoPoolTransactionError>(),
2413 Some(TempoPoolTransactionError::Evm(
2414 TempoInvalidTransaction::CallsValidation(_)
2415 ))
2416 ),
2417 "CREATE call as first call should be accepted, got: {err:?}"
2418 );
2419 }
2420 }
2421
2422 #[tokio::test]
2424 async fn test_aa_multiple_creates_rejected() {
2425 let current_time = std::time::SystemTime::now()
2426 .duration_since(std::time::UNIX_EPOCH)
2427 .unwrap()
2428 .as_secs();
2429
2430 let calls = vec![
2432 Call {
2433 to: TxKind::Create, value: U256::ZERO,
2435 input: Default::default(),
2436 },
2437 Call {
2438 to: TxKind::Call(Address::random()), value: U256::ZERO,
2440 input: Default::default(),
2441 },
2442 Call {
2443 to: TxKind::Create, value: U256::ZERO,
2445 input: Default::default(),
2446 },
2447 ];
2448
2449 let transaction = TxBuilder::aa(Address::random())
2450 .fee_token(address!("0000000000000000000000000000000000000002"))
2451 .calls(calls)
2452 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
2453 .build();
2454 let validator = setup_validator(&transaction, current_time);
2455
2456 let outcome = validator
2457 .validate_transaction(TransactionOrigin::External, transaction)
2458 .await;
2459
2460 match outcome {
2461 TransactionValidationOutcome::Invalid(_, ref err) => {
2462 assert!(
2463 matches!(
2464 err.downcast_other_ref::<TempoPoolTransactionError>(),
2465 Some(TempoPoolTransactionError::Evm(
2466 TempoInvalidTransaction::CallsValidation(_)
2467 ))
2468 ),
2469 "Expected CreateCallNotFirst error, got: {err:?}"
2470 );
2471 }
2472 _ => panic!("Expected Invalid outcome with CreateCallNotFirst error, got: {outcome:?}"),
2473 }
2474 }
2475
2476 #[tokio::test]
2478 async fn test_aa_create_call_with_authorization_list_rejected() {
2479 use alloy_eips::eip7702::Authorization;
2480 use alloy_primitives::Signature;
2481 use tempo_primitives::transaction::{
2482 TempoSignedAuthorization,
2483 tt_signature::{PrimitiveSignature, TempoSignature},
2484 };
2485
2486 let current_time = std::time::SystemTime::now()
2487 .duration_since(std::time::UNIX_EPOCH)
2488 .unwrap()
2489 .as_secs();
2490
2491 let calls = vec![Call {
2493 to: TxKind::Create, value: U256::ZERO,
2495 input: Default::default(),
2496 }];
2497
2498 let auth = Authorization {
2500 chain_id: U256::from(1),
2501 nonce: 0,
2502 address: address!("0000000000000000000000000000000000000001"),
2503 };
2504 let authorization = TempoSignedAuthorization::new_unchecked(
2505 auth,
2506 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())),
2507 );
2508
2509 let transaction = TxBuilder::aa(Address::random())
2510 .fee_token(address!("0000000000000000000000000000000000000002"))
2511 .calls(calls)
2512 .authorization_list(vec![authorization])
2513 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
2514 .build();
2515 let validator = setup_validator(&transaction, current_time);
2516
2517 let outcome = validator
2518 .validate_transaction(TransactionOrigin::External, transaction)
2519 .await;
2520
2521 match outcome {
2522 TransactionValidationOutcome::Invalid(_, ref err) => {
2523 assert!(
2524 matches!(
2525 err.downcast_other_ref::<TempoPoolTransactionError>(),
2526 Some(TempoPoolTransactionError::Evm(
2527 TempoInvalidTransaction::CallsValidation(_)
2528 ))
2529 ),
2530 "Expected CreateCallWithAuthorizationList error, got: {err:?}"
2531 );
2532 }
2533 _ => panic!("Expected Invalid outcome, got: {outcome:?}"),
2534 }
2535 }
2536
2537 #[test]
2539 fn test_paused_token_is_invalid_fee_token() {
2540 let fee_token = address!("20C0000000000000000000000000000000000001");
2541
2542 let usd_currency_value =
2544 uint!(0x5553440000000000000000000000000000000000000000000000000000000006_U256);
2545
2546 let provider =
2547 MockEthProvider::default().with_chain_spec(Arc::unwrap_or_clone(MODERATO.clone()));
2548 provider.add_account(
2549 fee_token,
2550 ExtendedAccount::new(0, U256::ZERO).extend_storage([
2551 (tip20_slots::CURRENCY.into(), usd_currency_value),
2552 (tip20_slots::PAUSED.into(), U256::from(1)),
2553 ]),
2554 );
2555
2556 let mut state = provider.latest().unwrap();
2557 let spec = provider.chain_spec().tempo_hardfork_at(0);
2558
2559 let result = state.is_fee_token_paused(spec, fee_token);
2561 assert!(result.is_ok());
2562 assert!(
2563 result.unwrap(),
2564 "Paused tokens should be detected as paused"
2565 );
2566 }
2567
2568 #[tokio::test]
2571 async fn test_non_aa_intrinsic_gas_insufficient_rejected() {
2572 let current_time = std::time::SystemTime::now()
2573 .duration_since(std::time::UNIX_EPOCH)
2574 .unwrap()
2575 .as_secs();
2576
2577 let tx = TxBuilder::eip1559(Address::random())
2579 .gas_limit(1_000) .build_eip1559();
2581
2582 let validator = setup_validator(&tx, current_time);
2583 let outcome = validator
2584 .validate_transaction(TransactionOrigin::External, tx)
2585 .await;
2586
2587 match outcome {
2588 TransactionValidationOutcome::Invalid(_, ref err) => {
2589 assert!(matches!(
2590 err.downcast_other_ref::<TempoPoolTransactionError>(),
2591 Some(TempoPoolTransactionError::Evm(
2592 TempoInvalidTransaction::EthInvalidTransaction(
2593 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
2594 )
2595 ))
2596 ))
2597 }
2598 TransactionValidationOutcome::Error(_, _) => {
2599 panic!("Expected Invalid outcome, got Error - this was the bug we fixed!")
2600 }
2601 _ => panic!("Expected Invalid outcome with IntrinsicGasTooLow, got: {outcome:?}"),
2602 }
2603 }
2604
2605 #[tokio::test]
2607 async fn test_non_aa_intrinsic_gas_sufficient_passes() {
2608 let current_time = std::time::SystemTime::now()
2609 .duration_since(std::time::UNIX_EPOCH)
2610 .unwrap()
2611 .as_secs();
2612
2613 let tx = TxBuilder::eip1559(Address::random())
2615 .gas_limit(1_000_000) .build_eip1559();
2617
2618 let validator = setup_validator(&tx, current_time);
2619 let outcome = validator
2620 .validate_transaction(TransactionOrigin::External, tx)
2621 .await;
2622
2623 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
2625 assert!(
2626 matches!(err, InvalidPoolTransactionError::IntrinsicGasTooLow),
2627 "Non-AA tx with 100k gas should NOT fail intrinsic gas check, got: {err:?}"
2628 );
2629 }
2630 }
2631
2632 #[tokio::test]
2634 async fn test_intrinsic_gas_error_contains_gas_details() {
2635 let current_time = std::time::SystemTime::now()
2636 .duration_since(std::time::UNIX_EPOCH)
2637 .unwrap()
2638 .as_secs();
2639
2640 let gas_limit = 5_000u64;
2641 let tx = TxBuilder::eip1559(Address::random())
2642 .gas_limit(gas_limit)
2643 .build_eip1559();
2644
2645 let validator = setup_validator(&tx, current_time);
2646 let outcome = validator
2647 .validate_transaction(TransactionOrigin::External, tx)
2648 .await;
2649
2650 match outcome {
2651 TransactionValidationOutcome::Invalid(_, ref err) => {
2652 assert!(matches!(
2653 err.downcast_other_ref::<TempoPoolTransactionError>(),
2654 Some(TempoPoolTransactionError::Evm(
2655 TempoInvalidTransaction::EthInvalidTransaction(
2656 InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
2657 )
2658 ))
2659 ));
2660 }
2661 _ => panic!("Expected Invalid outcome, got: {outcome:?}"),
2662 }
2663 }
2664
2665 #[test]
2667 fn test_paused_validator_token_rejected_before_liquidity_bypass() {
2668 let paused_validator_token = address!("20C0000000000000000000000000000000000001");
2670
2671 let usd_currency_value =
2673 uint!(0x5553440000000000000000000000000000000000000000000000000000000006_U256);
2674
2675 let provider =
2676 MockEthProvider::default().with_chain_spec(Arc::unwrap_or_clone(MODERATO.clone()));
2677
2678 provider.add_account(
2680 paused_validator_token,
2681 ExtendedAccount::new(0, U256::ZERO).extend_storage([
2682 (tip20_slots::CURRENCY.into(), usd_currency_value),
2683 (tip20_slots::PAUSED.into(), U256::from(1)),
2684 ]),
2685 );
2686
2687 let mut state = provider.latest().unwrap();
2688 let spec = provider.chain_spec().tempo_hardfork_at(0);
2689
2690 let amm_cache = AmmLiquidityCache::with_unique_tokens(vec![paused_validator_token]);
2694
2695 assert!(
2697 amm_cache.is_active_validator_token(&paused_validator_token),
2698 "Token should be in unique_tokens for this test"
2699 );
2700
2701 let liquidity_result =
2704 amm_cache.has_enough_liquidity(paused_validator_token, U256::from(1000), &state);
2705 assert!(
2706 liquidity_result.is_ok() && liquidity_result.unwrap(),
2707 "Token in unique_tokens should bypass liquidity check and return true"
2708 );
2709
2710 let is_paused = state.is_fee_token_paused(spec, paused_validator_token);
2712 assert!(is_paused.is_ok());
2713 assert!(
2714 is_paused.unwrap(),
2715 "Paused validator token should be detected by is_fee_token_paused BEFORE reaching has_enough_liquidity"
2716 );
2717 }
2718
2719 #[tokio::test]
2720 async fn test_aa_exactly_max_calls_accepted() {
2721 let current_time = std::time::SystemTime::now()
2722 .duration_since(std::time::UNIX_EPOCH)
2723 .unwrap()
2724 .as_secs();
2725
2726 let calls: Vec<Call> = (0..MAX_AA_CALLS)
2727 .map(|_| Call {
2728 to: TxKind::Call(Address::random()),
2729 value: U256::ZERO,
2730 input: Default::default(),
2731 })
2732 .collect();
2733
2734 let transaction = TxBuilder::aa(Address::random())
2735 .fee_token(address!("0000000000000000000000000000000000000002"))
2736 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
2737 .calls(calls)
2738 .build();
2739 let validator = setup_validator(&transaction, current_time);
2740
2741 let outcome = validator
2742 .validate_transaction(TransactionOrigin::External, transaction)
2743 .await;
2744
2745 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
2746 assert!(
2747 !matches!(
2748 err.downcast_other_ref::<TempoPoolTransactionError>(),
2749 Some(TempoPoolTransactionError::TooManyCalls { .. })
2750 ),
2751 "Exactly MAX_AA_CALLS calls should not trigger TooManyCalls, got: {err:?}"
2752 );
2753 }
2754 }
2755
2756 #[tokio::test]
2757 async fn test_aa_too_many_calls_rejected() {
2758 let current_time = std::time::SystemTime::now()
2759 .duration_since(std::time::UNIX_EPOCH)
2760 .unwrap()
2761 .as_secs();
2762
2763 let calls: Vec<Call> = (0..MAX_AA_CALLS + 1)
2764 .map(|_| Call {
2765 to: TxKind::Call(Address::random()),
2766 value: U256::ZERO,
2767 input: Default::default(),
2768 })
2769 .collect();
2770
2771 let transaction = TxBuilder::aa(Address::random())
2772 .fee_token(address!("0000000000000000000000000000000000000002"))
2773 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
2774 .calls(calls)
2775 .build();
2776 let validator = setup_validator(&transaction, current_time);
2777
2778 let outcome = validator
2779 .validate_transaction(TransactionOrigin::External, transaction)
2780 .await;
2781
2782 match outcome {
2783 TransactionValidationOutcome::Invalid(_, ref err) => {
2784 assert!(
2785 matches!(
2786 err.downcast_other_ref::<TempoPoolTransactionError>(),
2787 Some(TempoPoolTransactionError::TooManyCalls { .. })
2788 ),
2789 "Expected TooManyCalls error, got: {err:?}"
2790 );
2791 }
2792 _ => panic!("Expected Invalid outcome with TooManyCalls error, got: {outcome:?}"),
2793 }
2794 }
2795
2796 #[tokio::test]
2797 async fn test_aa_exactly_max_call_input_size_accepted() {
2798 let current_time = std::time::SystemTime::now()
2799 .duration_since(std::time::UNIX_EPOCH)
2800 .unwrap()
2801 .as_secs();
2802
2803 let calls = vec![Call {
2804 to: TxKind::Call(Address::random()),
2805 value: U256::ZERO,
2806 input: vec![0u8; MAX_CALL_INPUT_SIZE].into(),
2807 }];
2808
2809 let transaction = TxBuilder::aa(Address::random())
2810 .fee_token(address!("0000000000000000000000000000000000000002"))
2811 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
2812 .calls(calls)
2813 .build();
2814 let validator = setup_validator(&transaction, current_time);
2815
2816 let outcome = validator
2817 .validate_transaction(TransactionOrigin::External, transaction)
2818 .await;
2819
2820 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
2821 assert!(
2822 !matches!(
2823 err.downcast_other_ref::<TempoPoolTransactionError>(),
2824 Some(TempoPoolTransactionError::CallInputTooLarge { .. })
2825 ),
2826 "Exactly MAX_CALL_INPUT_SIZE input should not trigger CallInputTooLarge, got: {err:?}"
2827 );
2828 }
2829 }
2830
2831 #[tokio::test]
2832 async fn test_aa_call_input_too_large_rejected() {
2833 let current_time = std::time::SystemTime::now()
2834 .duration_since(std::time::UNIX_EPOCH)
2835 .unwrap()
2836 .as_secs();
2837
2838 let calls = vec![Call {
2839 to: TxKind::Call(Address::random()),
2840 value: U256::ZERO,
2841 input: vec![0u8; MAX_CALL_INPUT_SIZE + 1].into(),
2842 }];
2843
2844 let transaction = TxBuilder::aa(Address::random())
2845 .fee_token(address!("0000000000000000000000000000000000000002"))
2846 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
2847 .calls(calls)
2848 .build();
2849 let validator = setup_validator(&transaction, current_time);
2850
2851 let outcome = validator
2852 .validate_transaction(TransactionOrigin::External, transaction)
2853 .await;
2854
2855 match outcome {
2856 TransactionValidationOutcome::Invalid(_, ref err) => {
2857 let is_oversized = matches!(err, InvalidPoolTransactionError::OversizedData { .. });
2858 let is_call_input_too_large = matches!(
2859 err.downcast_other_ref::<TempoPoolTransactionError>(),
2860 Some(TempoPoolTransactionError::CallInputTooLarge { .. })
2861 );
2862 assert!(
2863 is_oversized || is_call_input_too_large,
2864 "Expected OversizedData or CallInputTooLarge error, got: {err:?}"
2865 );
2866 }
2867 _ => panic!("Expected Invalid outcome, got: {outcome:?}"),
2868 }
2869 }
2870
2871 #[tokio::test]
2872 async fn test_aa_exactly_max_access_list_accounts_accepted() {
2873 use alloy_eips::eip2930::{AccessList, AccessListItem};
2874
2875 let current_time = std::time::SystemTime::now()
2876 .duration_since(std::time::UNIX_EPOCH)
2877 .unwrap()
2878 .as_secs();
2879
2880 let items: Vec<AccessListItem> = (0..MAX_ACCESS_LIST_ACCOUNTS)
2881 .map(|_| AccessListItem {
2882 address: Address::random(),
2883 storage_keys: vec![],
2884 })
2885 .collect();
2886
2887 let transaction = TxBuilder::aa(Address::random())
2888 .fee_token(address!("0000000000000000000000000000000000000002"))
2889 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
2890 .access_list(AccessList(items))
2891 .build();
2892 let validator = setup_validator(&transaction, current_time);
2893
2894 let outcome = validator
2895 .validate_transaction(TransactionOrigin::External, transaction)
2896 .await;
2897
2898 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
2899 assert!(
2900 !matches!(
2901 err.downcast_other_ref::<TempoPoolTransactionError>(),
2902 Some(TempoPoolTransactionError::TooManyAccessListAccounts { .. })
2903 ),
2904 "Exactly MAX_ACCESS_LIST_ACCOUNTS should not trigger TooManyAccessListAccounts, got: {err:?}"
2905 );
2906 }
2907 }
2908
2909 #[tokio::test]
2910 async fn test_aa_too_many_access_list_accounts_rejected() {
2911 use alloy_eips::eip2930::{AccessList, AccessListItem};
2912
2913 let current_time = std::time::SystemTime::now()
2914 .duration_since(std::time::UNIX_EPOCH)
2915 .unwrap()
2916 .as_secs();
2917
2918 let items: Vec<AccessListItem> = (0..MAX_ACCESS_LIST_ACCOUNTS + 1)
2919 .map(|_| AccessListItem {
2920 address: Address::random(),
2921 storage_keys: vec![],
2922 })
2923 .collect();
2924
2925 let transaction = TxBuilder::aa(Address::random())
2926 .fee_token(address!("0000000000000000000000000000000000000002"))
2927 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
2928 .access_list(AccessList(items))
2929 .build();
2930 let validator = setup_validator(&transaction, current_time);
2931
2932 let outcome = validator
2933 .validate_transaction(TransactionOrigin::External, transaction)
2934 .await;
2935
2936 match outcome {
2937 TransactionValidationOutcome::Invalid(_, ref err) => {
2938 assert!(
2939 matches!(
2940 err.downcast_other_ref::<TempoPoolTransactionError>(),
2941 Some(TempoPoolTransactionError::TooManyAccessListAccounts { .. })
2942 ),
2943 "Expected TooManyAccessListAccounts error, got: {err:?}"
2944 );
2945 }
2946 _ => panic!(
2947 "Expected Invalid outcome with TooManyAccessListAccounts error, got: {outcome:?}"
2948 ),
2949 }
2950 }
2951
2952 #[tokio::test]
2953 async fn test_aa_exactly_max_storage_keys_per_account_accepted() {
2954 use alloy_eips::eip2930::{AccessList, AccessListItem};
2955
2956 let current_time = std::time::SystemTime::now()
2957 .duration_since(std::time::UNIX_EPOCH)
2958 .unwrap()
2959 .as_secs();
2960
2961 let items = vec![AccessListItem {
2962 address: Address::random(),
2963 storage_keys: (0..MAX_STORAGE_KEYS_PER_ACCOUNT)
2964 .map(|_| B256::random())
2965 .collect(),
2966 }];
2967
2968 let transaction = TxBuilder::aa(Address::random())
2969 .fee_token(address!("0000000000000000000000000000000000000002"))
2970 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
2971 .access_list(AccessList(items))
2972 .build();
2973 let validator = setup_validator(&transaction, current_time);
2974
2975 let outcome = validator
2976 .validate_transaction(TransactionOrigin::External, transaction)
2977 .await;
2978
2979 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
2980 assert!(
2981 !matches!(
2982 err.downcast_other_ref::<TempoPoolTransactionError>(),
2983 Some(TempoPoolTransactionError::TooManyStorageKeysPerAccount { .. })
2984 ),
2985 "Exactly MAX_STORAGE_KEYS_PER_ACCOUNT should not trigger TooManyStorageKeysPerAccount, got: {err:?}"
2986 );
2987 }
2988 }
2989
2990 #[tokio::test]
2991 async fn test_aa_too_many_storage_keys_per_account_rejected() {
2992 use alloy_eips::eip2930::{AccessList, AccessListItem};
2993
2994 let current_time = std::time::SystemTime::now()
2995 .duration_since(std::time::UNIX_EPOCH)
2996 .unwrap()
2997 .as_secs();
2998
2999 let items = vec![AccessListItem {
3000 address: Address::random(),
3001 storage_keys: (0..MAX_STORAGE_KEYS_PER_ACCOUNT + 1)
3002 .map(|_| B256::random())
3003 .collect(),
3004 }];
3005
3006 let transaction = TxBuilder::aa(Address::random())
3007 .fee_token(address!("0000000000000000000000000000000000000002"))
3008 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
3009 .access_list(AccessList(items))
3010 .build();
3011 let validator = setup_validator(&transaction, current_time);
3012
3013 let outcome = validator
3014 .validate_transaction(TransactionOrigin::External, transaction)
3015 .await;
3016
3017 match outcome {
3018 TransactionValidationOutcome::Invalid(_, ref err) => {
3019 assert!(
3020 matches!(
3021 err.downcast_other_ref::<TempoPoolTransactionError>(),
3022 Some(TempoPoolTransactionError::TooManyStorageKeysPerAccount { .. })
3023 ),
3024 "Expected TooManyStorageKeysPerAccount error, got: {err:?}"
3025 );
3026 }
3027 _ => panic!(
3028 "Expected Invalid outcome with TooManyStorageKeysPerAccount error, got: {outcome:?}"
3029 ),
3030 }
3031 }
3032
3033 #[tokio::test]
3034 async fn test_aa_exactly_max_total_storage_keys_accepted() {
3035 use alloy_eips::eip2930::{AccessList, AccessListItem};
3036
3037 let current_time = std::time::SystemTime::now()
3038 .duration_since(std::time::UNIX_EPOCH)
3039 .unwrap()
3040 .as_secs();
3041
3042 let keys_per_account = MAX_STORAGE_KEYS_PER_ACCOUNT;
3043 let num_accounts = MAX_ACCESS_LIST_STORAGE_KEYS_TOTAL / keys_per_account;
3044 let items: Vec<AccessListItem> = (0..num_accounts)
3045 .map(|_| AccessListItem {
3046 address: Address::random(),
3047 storage_keys: (0..keys_per_account).map(|_| B256::random()).collect(),
3048 })
3049 .collect();
3050 assert_eq!(
3051 items.iter().map(|i| i.storage_keys.len()).sum::<usize>(),
3052 MAX_ACCESS_LIST_STORAGE_KEYS_TOTAL
3053 );
3054
3055 let transaction = TxBuilder::aa(Address::random())
3056 .fee_token(address!("0000000000000000000000000000000000000002"))
3057 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
3058 .access_list(AccessList(items))
3059 .build();
3060 let validator = setup_validator(&transaction, current_time);
3061
3062 let outcome = validator
3063 .validate_transaction(TransactionOrigin::External, transaction)
3064 .await;
3065
3066 if let TransactionValidationOutcome::Invalid(_, ref err) = outcome {
3067 assert!(
3068 !matches!(
3069 err.downcast_other_ref::<TempoPoolTransactionError>(),
3070 Some(TempoPoolTransactionError::TooManyTotalStorageKeys { .. })
3071 ),
3072 "Exactly MAX_ACCESS_LIST_STORAGE_KEYS_TOTAL should not trigger TooManyTotalStorageKeys, got: {err:?}"
3073 );
3074 }
3075 }
3076
3077 #[tokio::test]
3078 async fn test_aa_too_many_total_storage_keys_rejected() {
3079 use alloy_eips::eip2930::{AccessList, AccessListItem};
3080
3081 let current_time = std::time::SystemTime::now()
3082 .duration_since(std::time::UNIX_EPOCH)
3083 .unwrap()
3084 .as_secs();
3085
3086 let keys_per_account = MAX_STORAGE_KEYS_PER_ACCOUNT;
3087 let num_accounts = MAX_ACCESS_LIST_STORAGE_KEYS_TOTAL / keys_per_account;
3088 let mut items: Vec<AccessListItem> = (0..num_accounts)
3089 .map(|_| AccessListItem {
3090 address: Address::random(),
3091 storage_keys: (0..keys_per_account).map(|_| B256::random()).collect(),
3092 })
3093 .collect();
3094 items.push(AccessListItem {
3095 address: Address::random(),
3096 storage_keys: vec![B256::random()],
3097 });
3098 assert_eq!(
3099 items.iter().map(|i| i.storage_keys.len()).sum::<usize>(),
3100 MAX_ACCESS_LIST_STORAGE_KEYS_TOTAL + 1
3101 );
3102
3103 let transaction = TxBuilder::aa(Address::random())
3104 .fee_token(address!("0000000000000000000000000000000000000002"))
3105 .gas_limit(TEMPO_T1_TX_GAS_LIMIT_CAP)
3106 .access_list(AccessList(items))
3107 .build();
3108 let validator = setup_validator(&transaction, current_time);
3109
3110 let outcome = validator
3111 .validate_transaction(TransactionOrigin::External, transaction)
3112 .await;
3113
3114 match outcome {
3115 TransactionValidationOutcome::Invalid(_, ref err) => {
3116 assert!(
3117 matches!(
3118 err.downcast_other_ref::<TempoPoolTransactionError>(),
3119 Some(TempoPoolTransactionError::TooManyTotalStorageKeys { .. })
3120 ),
3121 "Expected TooManyTotalStorageKeys error, got: {err:?}"
3122 );
3123 }
3124 _ => panic!(
3125 "Expected Invalid outcome with TooManyTotalStorageKeys error, got: {outcome:?}"
3126 ),
3127 }
3128 }
3129}