1use crate::{
6 amm::AmmLiquidityCache, best::MergeBestTransactions, transaction::TempoPooledTransaction,
7 tt_2d_pool::AA2dPool, validator::TempoTransactionValidator,
8};
9use alloy_consensus::Transaction;
10use alloy_primitives::{
11 Address, B256, TxHash, U256,
12 map::{AddressMap, AddressSet, HashMap},
13};
14use parking_lot::RwLock;
15use reth_chainspec::ChainSpecProvider;
16use reth_eth_wire_types::HandleMempoolData;
17use reth_provider::{ChangedAccount, StateProviderFactory};
18use reth_storage_api::StateProvider;
19use reth_transaction_pool::{
20 AddedTransactionOutcome, AllPoolTransactions, BestTransactions, BestTransactionsAttributes,
21 BlockInfo, CanonicalStateUpdate, CoinbaseTipOrdering, GetPooledTransactionLimit,
22 NewBlobSidecar, Pool, PoolResult, PoolSize, PoolTransaction, PropagatedTransactions,
23 TransactionEvents, TransactionOrigin, TransactionPool, TransactionPoolExt,
24 TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator,
25 ValidPoolTransaction,
26 blobstore::InMemoryBlobStore,
27 error::{PoolError, PoolErrorKind},
28 identifier::TransactionId,
29};
30use revm::database::BundleAccount;
31use std::{sync::Arc, time::Instant};
32use tempo_chainspec::{
33 TempoChainSpec,
34 hardfork::{TempoHardfork, TempoHardforks},
35};
36use tempo_precompiles::{
37 DEFAULT_FEE_TOKEN, TIP_FEE_MANAGER_ADDRESS,
38 account_keychain::AccountKeychain,
39 error::Result as TempoPrecompileResult,
40 storage::Handler,
41 tip20::TIP20Token,
42 tip403_registry::{REJECT_ALL_POLICY_ID, TIP403Registry},
43};
44use tempo_primitives::Block;
45use tempo_revm::TempoStateAccess;
46
47pub struct TempoTransactionPool<Client> {
49 protocol_pool: Pool<
51 TransactionValidationTaskExecutor<TempoTransactionValidator<Client>>,
52 CoinbaseTipOrdering<TempoPooledTransaction>,
53 InMemoryBlobStore,
54 >,
55 aa_2d_pool: Arc<RwLock<AA2dPool>>,
57}
58
59impl<Client> TempoTransactionPool<Client> {
60 pub fn new(
61 protocol_pool: Pool<
62 TransactionValidationTaskExecutor<TempoTransactionValidator<Client>>,
63 CoinbaseTipOrdering<TempoPooledTransaction>,
64 InMemoryBlobStore,
65 >,
66 aa_2d_pool: AA2dPool,
67 ) -> Self {
68 Self {
69 protocol_pool,
70 aa_2d_pool: Arc::new(RwLock::new(aa_2d_pool)),
71 }
72 }
73}
74impl<Client> TempoTransactionPool<Client>
75where
76 Client: StateProviderFactory + ChainSpecProvider<ChainSpec = TempoChainSpec> + 'static,
77{
78 pub fn amm_liquidity_cache(&self) -> AmmLiquidityCache {
80 self.protocol_pool
81 .validator()
82 .validator()
83 .amm_liquidity_cache()
84 }
85
86 pub fn client(&self) -> &Client {
88 self.protocol_pool.validator().validator().client()
89 }
90
91 pub(crate) fn notify_aa_pool_on_state_updates(&self, state: &AddressMap<BundleAccount>) {
93 let (promoted, _mined) = self.aa_2d_pool.write().on_state_updates(state);
94 self.protocol_pool
96 .inner()
97 .notify_on_transaction_updates(promoted, Vec::new());
98 }
99
100 pub fn evict_invalidated_transactions(
116 &self,
117 updates: &crate::maintain::TempoPoolUpdates,
118 ) -> Vec<TxHash> {
119 if !updates.has_invalidation_events() {
120 return Vec::new();
121 }
122
123 let mut state_provider = if !updates.validator_token_changes.is_empty()
129 || !updates.blacklist_additions.is_empty()
130 || !updates.whitelist_removals.is_empty()
131 || !updates.fee_balance_changes.is_empty()
132 || !updates.spending_limit_spends.is_empty()
133 {
134 self.client().latest().ok()
135 } else {
136 None
137 };
138
139 let tip_timestamp = self
141 .protocol_pool
142 .validator()
143 .validator()
144 .inner
145 .fork_tracker()
146 .tip_timestamp();
147 let spec = self.client().chain_spec().tempo_hardfork_at(tip_timestamp);
148
149 let mut policy_cache: AddressMap<Vec<u64>> = AddressMap::default();
153
154 let fee_manager_blacklisted: Vec<u64> = updates
158 .blacklist_additions
159 .iter()
160 .filter(|(_, account)| *account == TIP_FEE_MANAGER_ADDRESS)
161 .map(|(policy_id, _)| *policy_id)
162 .collect();
163 let fee_manager_unwhitelisted: Vec<u64> = updates
164 .whitelist_removals
165 .iter()
166 .filter(|(_, account)| *account == TIP_FEE_MANAGER_ADDRESS)
167 .map(|(policy_id, _)| *policy_id)
168 .collect();
169
170 let amm_cache = self.amm_liquidity_cache();
174 let has_active_validator_token_changes = !updates.validator_token_changes.is_empty() && {
175 let active_new_tokens: Vec<_> = updates
176 .validator_token_changes
177 .iter()
178 .filter(|(validator, _)| amm_cache.is_active_validator(validator))
179 .filter(|(_, new_token)| !amm_cache.is_active_validator_token(new_token))
180 .map(|(_, &new_token)| new_token)
181 .collect();
182 amm_cache.track_tokens(&active_new_tokens)
183 };
184
185 let mut to_remove = Vec::new();
186 let mut revoked_count = 0;
187 let mut spending_limit_count = 0;
188 let mut spending_limit_spend_count = 0;
189 let mut liquidity_count = 0;
190 let mut user_token_count = 0;
191 let mut blacklisted_count = 0;
192 let mut unwhitelisted_count = 0;
193 let mut insolvent_fee_payer_count = 0;
194 let mut fee_balance_cache: HashMap<(Address, Address), U256> = HashMap::default();
195
196 let all_txs = self.all_transactions();
197 for tx in all_txs.pending.iter().chain(all_txs.queued.iter()) {
198 let keychain_subject = tx.transaction.keychain_subject();
200
201 if !updates.revoked_keys.is_empty()
203 && let Some(ref subject) = keychain_subject
204 && subject.matches_revoked(&updates.revoked_keys)
205 {
206 to_remove.push(*tx.hash());
207 revoked_count += 1;
208 continue;
209 }
210
211 if !updates.spending_limit_changes.is_empty()
214 && let Some(ref subject) = keychain_subject
215 && subject.matches_spending_limit_update(&updates.spending_limit_changes)
216 {
217 to_remove.push(*tx.hash());
218 spending_limit_count += 1;
219 continue;
220 }
221
222 if !updates.spending_limit_spends.is_empty()
228 && let Some(ref subject) = keychain_subject
229 && subject.matches_spending_limit_update(&updates.spending_limit_spends)
230 && let Some(ref mut provider) = state_provider
231 && exceeds_spending_limit(
232 provider,
233 subject,
234 tx.transaction.fee_token_cost(),
235 tip_timestamp,
236 spec,
237 )
238 {
239 to_remove.push(*tx.hash());
240 spending_limit_spend_count += 1;
241 continue;
242 }
243
244 if has_active_validator_token_changes && let Some(ref mut provider) = state_provider {
249 let user_token = tx
250 .transaction
251 .inner()
252 .fee_token()
253 .unwrap_or(tempo_precompiles::DEFAULT_FEE_TOKEN);
254 let cost = tx.transaction.fee_token_cost();
255
256 match amm_cache.has_enough_liquidity(user_token, cost, provider) {
257 Ok(true) => {}
258 Ok(false) => {
259 to_remove.push(*tx.hash());
260 liquidity_count += 1;
261 continue;
262 }
263 Err(_) => continue,
264 }
265 }
266
267 if !updates.fee_balance_changes.is_empty()
271 && let Some(ref mut provider) = state_provider
272 {
273 let fee_token = tx.transaction.resolved_fee_token().unwrap_or_else(|| {
274 tx.transaction
275 .inner()
276 .fee_token()
277 .unwrap_or(DEFAULT_FEE_TOKEN)
278 });
279 let Ok(fee_payer) = tx.transaction.inner().fee_payer(tx.transaction.sender())
280 else {
281 continue;
282 };
283
284 if updates
285 .fee_balance_changes
286 .get(&fee_token)
287 .is_some_and(|accounts| accounts.contains(&fee_payer))
288 {
289 let key = (fee_token, fee_payer);
290 let balance = if let Some(balance) = fee_balance_cache.get(&key).copied() {
291 balance
292 } else {
293 let Ok(balance) = provider.get_token_balance(fee_token, fee_payer, spec)
294 else {
295 continue;
296 };
297 fee_balance_cache.insert(key, balance);
298 balance
299 };
300
301 if balance < tx.transaction.fee_token_cost() {
302 to_remove.push(*tx.hash());
303 insolvent_fee_payer_count += 1;
304 continue;
305 }
306 }
307 }
308
309 if !updates.blacklist_additions.is_empty()
313 && let Some(ref mut provider) = state_provider
314 && let Some(fee_token) = tx.transaction.inner().fee_token()
315 {
316 let fee_payer = tx
317 .transaction
318 .inner()
319 .fee_payer(tx.transaction.sender())
320 .unwrap_or(tx.transaction.sender());
321
322 let mut sender_evicted = false;
324 for &(blacklist_policy_id, blacklisted_account) in &updates.blacklist_additions {
325 if fee_payer != blacklisted_account {
326 continue;
327 }
328
329 let token_policies =
330 get_sender_policy_ids(provider, fee_token, spec, &mut policy_cache);
331
332 if token_policies
333 .as_ref()
334 .is_some_and(|ids| ids.contains(&blacklist_policy_id))
335 {
336 sender_evicted = true;
337 break;
338 }
339 }
340
341 let recipient_evicted = !sender_evicted
345 && !fee_manager_blacklisted.is_empty()
346 && get_recipient_policy_ids(provider, fee_token, spec)
347 .is_some_and(|ids| fee_manager_blacklisted.iter().any(|p| ids.contains(p)));
348
349 if sender_evicted || recipient_evicted {
350 to_remove.push(*tx.hash());
351 blacklisted_count += 1;
352 }
353 }
354
355 if !updates.whitelist_removals.is_empty()
359 && let Some(ref mut provider) = state_provider
360 && let Some(fee_token) = tx.transaction.inner().fee_token()
361 {
362 let fee_payer = tx
363 .transaction
364 .inner()
365 .fee_payer(tx.transaction.sender())
366 .unwrap_or(tx.transaction.sender());
367
368 let mut sender_evicted = false;
369 for &(whitelist_policy_id, unwhitelisted_account) in &updates.whitelist_removals {
370 if fee_payer != unwhitelisted_account {
371 continue;
372 }
373
374 let token_policies =
375 get_sender_policy_ids(provider, fee_token, spec, &mut policy_cache);
376
377 if token_policies
378 .as_ref()
379 .is_some_and(|ids| ids.contains(&whitelist_policy_id))
380 {
381 sender_evicted = true;
382 break;
383 }
384 }
385
386 let recipient_evicted = !sender_evicted
389 && !fee_manager_unwhitelisted.is_empty()
390 && get_recipient_policy_ids(provider, fee_token, spec).is_some_and(|ids| {
391 fee_manager_unwhitelisted.iter().any(|p| ids.contains(p))
392 });
393
394 if sender_evicted || recipient_evicted {
395 to_remove.push(*tx.hash());
396 unwhitelisted_count += 1;
397 }
398 }
399
400 if !updates.user_token_changes.is_empty()
406 && tx.transaction.inner().fee_token().is_none()
407 && updates
408 .user_token_changes
409 .contains(&tx.transaction.sender())
410 {
411 to_remove.push(*tx.hash());
412 user_token_count += 1;
413 }
414 }
415
416 if !to_remove.is_empty() {
417 tracing::debug!(
418 target: "txpool",
419 total = to_remove.len(),
420 revoked_count,
421 spending_limit_count,
422 spending_limit_spend_count,
423 liquidity_count,
424 user_token_count,
425 blacklisted_count,
426 unwhitelisted_count,
427 insolvent_fee_payer_count,
428 "Evicting invalidated transactions"
429 );
430 self.remove_transactions(to_remove.clone());
431 }
432 to_remove
433 }
434
435 fn add_validated_transaction(
436 &self,
437 origin: TransactionOrigin,
438 transaction: TransactionValidationOutcome<TempoPooledTransaction>,
439 ) -> PoolResult<AddedTransactionOutcome> {
440 match transaction {
441 TransactionValidationOutcome::Valid {
442 balance,
443 state_nonce,
444 bytecode_hash,
445 transaction,
446 propagate,
447 authorities,
448 } => {
449 if transaction.transaction().is_aa_2d() {
450 let transaction = transaction.into_transaction();
451 let sender_id = self
452 .protocol_pool
453 .inner()
454 .get_sender_id(transaction.sender());
455 let transaction_id = TransactionId::new(sender_id, transaction.nonce());
456 let tx = ValidPoolTransaction {
457 transaction,
458 transaction_id,
459 propagate,
460 timestamp: Instant::now(),
461 origin,
462 authority_ids: authorities
463 .map(|auths| self.protocol_pool.inner().get_sender_ids(auths)),
464 };
465
466 let tip_timestamp = self
468 .protocol_pool
469 .validator()
470 .validator()
471 .inner
472 .fork_tracker()
473 .tip_timestamp();
474 let hardfork = self.client().chain_spec().tempo_hardfork_at(tip_timestamp);
475
476 let added = self.aa_2d_pool.write().add_transaction(
477 Arc::new(tx),
478 state_nonce,
479 hardfork,
480 )?;
481 let hash = *added.hash();
482 if let Some(pending) = added.as_pending() {
483 if pending.discarded.iter().any(|tx| *tx.hash() == hash) {
484 return Err(PoolError::new(hash, PoolErrorKind::DiscardedOnInsert));
485 }
486 self.protocol_pool
487 .inner()
488 .on_new_pending_transaction(pending);
489 }
490
491 let state = added.transaction_state();
492 self.protocol_pool.inner().notify_event_listeners(&added);
494 self.protocol_pool
495 .inner()
496 .on_new_transaction(added.into_new_transaction_event());
497
498 Ok(AddedTransactionOutcome { hash, state })
499 } else {
500 self.protocol_pool
501 .inner()
502 .add_transactions(
503 origin,
504 std::iter::once(TransactionValidationOutcome::Valid {
505 balance,
506 state_nonce,
507 bytecode_hash,
508 transaction,
509 propagate,
510 authorities,
511 }),
512 )
513 .pop()
514 .unwrap()
515 }
516 }
517 invalid => {
518 self.protocol_pool
520 .inner()
521 .add_transactions(origin, Some(invalid))
522 .pop()
523 .unwrap()
524 }
525 }
526 }
527}
528
529impl<Client> Clone for TempoTransactionPool<Client> {
531 fn clone(&self) -> Self {
532 Self {
533 protocol_pool: self.protocol_pool.clone(),
534 aa_2d_pool: Arc::clone(&self.aa_2d_pool),
535 }
536 }
537}
538
539impl<Client> std::fmt::Debug for TempoTransactionPool<Client> {
541 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542 f.debug_struct("TempoTransactionPool")
543 .field("protocol_pool", &"Pool<...>")
544 .field("aa_2d_nonce_pool", &"AA2dPool<...>")
545 .field("paused_fee_token_pool", &"PausedFeeTokenPool<...>")
546 .finish_non_exhaustive()
547 }
548}
549
550impl<Client> TransactionPool for TempoTransactionPool<Client>
552where
553 Client: StateProviderFactory
554 + ChainSpecProvider<ChainSpec = TempoChainSpec>
555 + Send
556 + Sync
557 + 'static,
558 TempoPooledTransaction: reth_transaction_pool::EthPoolTransaction,
559{
560 type Transaction = TempoPooledTransaction;
561
562 fn pool_size(&self) -> PoolSize {
563 let mut size = self.protocol_pool.pool_size();
564 let (pending, queued) = self.aa_2d_pool.read().pending_and_queued_txn_count();
565 size.pending += pending;
566 size.queued += queued;
567 size
568 }
569
570 fn block_info(&self) -> BlockInfo {
571 self.protocol_pool.block_info()
572 }
573
574 async fn add_transaction_and_subscribe(
575 &self,
576 origin: TransactionOrigin,
577 transaction: Self::Transaction,
578 ) -> PoolResult<TransactionEvents> {
579 let tx = self
580 .protocol_pool
581 .validator()
582 .validate_transaction(origin, transaction)
583 .await;
584 let res = self.add_validated_transaction(origin, tx)?;
585 self.transaction_event_listener(res.hash)
586 .ok_or_else(|| PoolError::new(res.hash, PoolErrorKind::DiscardedOnInsert))
587 }
588
589 async fn add_transaction(
590 &self,
591 origin: TransactionOrigin,
592 transaction: Self::Transaction,
593 ) -> PoolResult<AddedTransactionOutcome> {
594 let tx = self
595 .protocol_pool
596 .validator()
597 .validate_transaction(origin, transaction)
598 .await;
599 self.add_validated_transaction(origin, tx)
600 }
601
602 async fn add_transactions(
603 &self,
604 origin: TransactionOrigin,
605 transactions: Vec<Self::Transaction>,
606 ) -> Vec<PoolResult<AddedTransactionOutcome>> {
607 if transactions.is_empty() {
608 return Vec::new();
609 }
610
611 if !transactions.iter().any(|tx| tx.is_aa_2d()) {
613 return self
614 .protocol_pool
615 .add_transactions(origin, transactions)
616 .await;
617 }
618
619 self.protocol_pool
620 .validator()
621 .validate_transactions_with_origin(origin, transactions)
622 .await
623 .into_iter()
624 .map(|outcome| self.add_validated_transaction(origin, outcome))
625 .collect()
626 }
627
628 async fn add_transactions_with_origins(
629 &self,
630 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
631 ) -> Vec<PoolResult<AddedTransactionOutcome>> {
632 if transactions.is_empty() {
633 return Vec::new();
634 }
635
636 if !transactions.iter().any(|(_, tx)| tx.is_aa_2d()) {
638 return self
639 .protocol_pool
640 .add_transactions_with_origins(transactions)
641 .await;
642 }
643
644 let origins = transactions
645 .iter()
646 .map(|(origin, _)| *origin)
647 .collect::<Vec<_>>();
648
649 self.protocol_pool
650 .validator()
651 .validate_transactions(transactions)
652 .await
653 .into_iter()
654 .zip(origins)
655 .map(|(outcome, origin)| self.add_validated_transaction(origin, outcome))
656 .collect()
657 }
658
659 fn transaction_event_listener(&self, tx_hash: B256) -> Option<TransactionEvents> {
660 self.protocol_pool.transaction_event_listener(tx_hash)
661 }
662
663 fn all_transactions_event_listener(
664 &self,
665 ) -> reth_transaction_pool::AllTransactionsEvents<Self::Transaction> {
666 self.protocol_pool.all_transactions_event_listener()
667 }
668
669 fn pending_transactions_listener_for(
670 &self,
671 kind: reth_transaction_pool::TransactionListenerKind,
672 ) -> tokio::sync::mpsc::Receiver<B256> {
673 self.protocol_pool.pending_transactions_listener_for(kind)
674 }
675
676 fn blob_transaction_sidecars_listener(&self) -> tokio::sync::mpsc::Receiver<NewBlobSidecar> {
677 self.protocol_pool.blob_transaction_sidecars_listener()
678 }
679
680 fn new_transactions_listener_for(
681 &self,
682 kind: reth_transaction_pool::TransactionListenerKind,
683 ) -> tokio::sync::mpsc::Receiver<reth_transaction_pool::NewTransactionEvent<Self::Transaction>>
684 {
685 self.protocol_pool.new_transactions_listener_for(kind)
686 }
687
688 fn pooled_transaction_hashes(&self) -> Vec<B256> {
689 let mut hashes = self.protocol_pool.pooled_transaction_hashes();
690 hashes.extend(self.aa_2d_pool.read().pooled_transactions_hashes_iter());
691 hashes
692 }
693
694 fn pooled_transaction_hashes_max(&self, max: usize) -> Vec<B256> {
695 let protocol_hashes = self.protocol_pool.pooled_transaction_hashes_max(max);
696 if protocol_hashes.len() >= max {
697 return protocol_hashes;
698 }
699 let remaining = max - protocol_hashes.len();
700 let mut hashes = protocol_hashes;
701 hashes.extend(
702 self.aa_2d_pool
703 .read()
704 .pooled_transactions_hashes_iter()
705 .take(remaining),
706 );
707 hashes
708 }
709
710 fn pooled_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
711 let mut txs = self.protocol_pool.pooled_transactions();
712 txs.extend(self.aa_2d_pool.read().pooled_transactions_iter());
713 txs
714 }
715
716 fn pooled_transactions_max(
717 &self,
718 max: usize,
719 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
720 let mut txs = self.protocol_pool.pooled_transactions_max(max);
721 if txs.len() >= max {
722 return txs;
723 }
724
725 let remaining = max - txs.len();
726 txs.extend(
727 self.aa_2d_pool
728 .read()
729 .pooled_transactions_iter()
730 .take(remaining),
731 );
732 txs
733 }
734
735 fn get_pooled_transaction_elements(
736 &self,
737 tx_hashes: Vec<B256>,
738 limit: GetPooledTransactionLimit,
739 ) -> Vec<<Self::Transaction as PoolTransaction>::Pooled> {
740 let mut out = Vec::new();
741 self.append_pooled_transaction_elements(&tx_hashes, limit, &mut out);
742 out
743 }
744
745 fn append_pooled_transaction_elements(
746 &self,
747 tx_hashes: &[B256],
748 limit: GetPooledTransactionLimit,
749 out: &mut Vec<<Self::Transaction as PoolTransaction>::Pooled>,
750 ) {
751 let mut accumulated_size = 0;
752 self.aa_2d_pool.read().append_pooled_transaction_elements(
753 tx_hashes,
754 limit,
755 &mut accumulated_size,
756 out,
757 );
758
759 if limit.exceeds(accumulated_size) {
761 return;
762 }
763
764 let remaining_limit = match limit {
766 GetPooledTransactionLimit::None => GetPooledTransactionLimit::None,
767 GetPooledTransactionLimit::ResponseSizeSoftLimit(max) => {
768 GetPooledTransactionLimit::ResponseSizeSoftLimit(
769 max.saturating_sub(accumulated_size),
770 )
771 }
772 };
773
774 self.protocol_pool
775 .append_pooled_transaction_elements(tx_hashes, remaining_limit, out);
776 }
777
778 fn get_pooled_transaction_element(
779 &self,
780 tx_hash: B256,
781 ) -> Option<reth_primitives_traits::Recovered<<Self::Transaction as PoolTransaction>::Pooled>>
782 {
783 self.protocol_pool
784 .get_pooled_transaction_element(tx_hash)
785 .or_else(|| {
786 self.aa_2d_pool
787 .read()
788 .get(&tx_hash)
789 .and_then(|tx| tx.transaction.clone_into_pooled().ok())
790 })
791 }
792
793 fn best_transactions(
794 &self,
795 ) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>> {
796 let left = self.protocol_pool.inner().best_transactions();
797 let right = self.aa_2d_pool.read().best_transactions();
798 Box::new(MergeBestTransactions::new(left, right))
799 }
800
801 fn best_transactions_with_attributes(
802 &self,
803 _attributes: BestTransactionsAttributes,
804 ) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>> {
805 self.best_transactions()
806 }
807
808 fn pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
809 let mut pending = self.protocol_pool.pending_transactions();
810 pending.extend(self.aa_2d_pool.read().pending_transactions());
811 pending
812 }
813
814 fn pending_transactions_max(
815 &self,
816 max: usize,
817 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
818 let protocol_txs = self.protocol_pool.pending_transactions_max(max);
819 if protocol_txs.len() >= max {
820 return protocol_txs;
821 }
822 let remaining = max - protocol_txs.len();
823 let mut txs = protocol_txs;
824 txs.extend(
825 self.aa_2d_pool
826 .read()
827 .pending_transactions()
828 .take(remaining),
829 );
830 txs
831 }
832
833 fn queued_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
834 let mut queued = self.protocol_pool.queued_transactions();
835 queued.extend(self.aa_2d_pool.read().queued_transactions());
836 queued
837 }
838
839 fn pending_and_queued_txn_count(&self) -> (usize, usize) {
840 let (protocol_pending, protocol_queued) = self.protocol_pool.pending_and_queued_txn_count();
841 let (aa_pending, aa_queued) = self.aa_2d_pool.read().pending_and_queued_txn_count();
842 (protocol_pending + aa_pending, protocol_queued + aa_queued)
843 }
844
845 fn all_transactions(&self) -> AllPoolTransactions<Self::Transaction> {
846 let mut transactions = self.protocol_pool.all_transactions();
847 {
848 let aa_2d_pool = self.aa_2d_pool.read();
849 transactions
850 .pending
851 .extend(aa_2d_pool.pending_transactions());
852 transactions.queued.extend(aa_2d_pool.queued_transactions());
853 }
854 transactions
855 }
856
857 fn all_transaction_hashes(&self) -> Vec<B256> {
858 let mut hashes = self.protocol_pool.all_transaction_hashes();
859 hashes.extend(self.aa_2d_pool.read().all_transaction_hashes_iter());
860 hashes
861 }
862
863 fn remove_transactions(
864 &self,
865 hashes: Vec<B256>,
866 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
867 let mut txs = self.aa_2d_pool.write().remove_transactions(hashes.iter());
868 txs.extend(self.protocol_pool.remove_transactions(hashes));
869 txs
870 }
871
872 fn remove_transactions_and_descendants(
873 &self,
874 hashes: Vec<B256>,
875 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
876 let mut txs = self
877 .aa_2d_pool
878 .write()
879 .remove_transactions_and_descendants(hashes.iter());
880 txs.extend(
881 self.protocol_pool
882 .remove_transactions_and_descendants(hashes),
883 );
884 txs
885 }
886
887 fn remove_transactions_by_sender(
888 &self,
889 sender: Address,
890 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
891 let mut txs = self
892 .aa_2d_pool
893 .write()
894 .remove_transactions_by_sender(sender);
895 txs.extend(self.protocol_pool.remove_transactions_by_sender(sender));
896 txs
897 }
898
899 fn prune_transactions(
900 &self,
901 hashes: Vec<TxHash>,
902 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
903 let mut txs = self.aa_2d_pool.write().remove_transactions(hashes.iter());
904 txs.extend(self.protocol_pool.prune_transactions(hashes));
905 txs
906 }
907
908 fn retain_unknown<A: HandleMempoolData>(&self, announcement: &mut A) {
909 self.protocol_pool.retain_unknown(announcement);
910 if announcement.is_empty() {
911 return;
912 }
913 let aa_pool = self.aa_2d_pool.read();
914 announcement.retain_by_hash(|tx| !aa_pool.contains(tx))
915 }
916
917 fn contains(&self, tx_hash: &B256) -> bool {
918 self.protocol_pool.contains(tx_hash) || self.aa_2d_pool.read().contains(tx_hash)
919 }
920
921 fn get(&self, tx_hash: &B256) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>> {
922 self.protocol_pool
923 .get(tx_hash)
924 .or_else(|| self.aa_2d_pool.read().get(tx_hash))
925 }
926
927 fn get_all(&self, txs: Vec<B256>) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
928 let mut result = self.aa_2d_pool.read().get_all(txs.iter());
929 result.extend(self.protocol_pool.get_all(txs));
930 result
931 }
932
933 fn on_propagated(&self, txs: PropagatedTransactions) {
934 self.protocol_pool.on_propagated(txs);
935 }
936
937 fn get_transactions_by_sender(
938 &self,
939 sender: Address,
940 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
941 let mut txs = self.protocol_pool.get_transactions_by_sender(sender);
942 txs.extend(
943 self.aa_2d_pool
944 .read()
945 .get_transactions_by_sender_iter(sender),
946 );
947 txs
948 }
949
950 fn get_pending_transactions_with_predicate(
951 &self,
952 mut predicate: impl FnMut(&ValidPoolTransaction<Self::Transaction>) -> bool,
953 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
954 let mut txs = self
955 .protocol_pool
956 .get_pending_transactions_with_predicate(&mut predicate);
957 txs.extend(
958 self.aa_2d_pool
959 .read()
960 .pending_transactions()
961 .filter(|tx| predicate(tx)),
962 );
963 txs
964 }
965
966 fn get_pending_transactions_by_sender(
967 &self,
968 sender: Address,
969 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
970 let mut txs = self
971 .protocol_pool
972 .get_pending_transactions_by_sender(sender);
973 txs.extend(
974 self.aa_2d_pool
975 .read()
976 .pending_transactions()
977 .filter(|tx| tx.sender() == sender),
978 );
979
980 txs
981 }
982
983 fn get_queued_transactions_by_sender(
984 &self,
985 sender: Address,
986 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
987 self.protocol_pool.get_queued_transactions_by_sender(sender)
988 }
989
990 fn get_highest_transaction_by_sender(
991 &self,
992 sender: Address,
993 ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>> {
994 self.protocol_pool.get_highest_transaction_by_sender(sender)
997 }
998
999 fn get_highest_consecutive_transaction_by_sender(
1000 &self,
1001 sender: Address,
1002 on_chain_nonce: u64,
1003 ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>> {
1004 self.protocol_pool
1006 .get_highest_consecutive_transaction_by_sender(sender, on_chain_nonce)
1007 }
1008
1009 fn get_transaction_by_sender_and_nonce(
1010 &self,
1011 sender: Address,
1012 nonce: u64,
1013 ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>> {
1014 self.protocol_pool
1016 .get_transaction_by_sender_and_nonce(sender, nonce)
1017 }
1018
1019 fn get_transactions_by_origin(
1020 &self,
1021 origin: TransactionOrigin,
1022 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
1023 let mut txs = self.protocol_pool.get_transactions_by_origin(origin);
1024 txs.extend(
1025 self.aa_2d_pool
1026 .read()
1027 .get_transactions_by_origin_iter(origin),
1028 );
1029 txs
1030 }
1031
1032 fn get_pending_transactions_by_origin(
1033 &self,
1034 origin: TransactionOrigin,
1035 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
1036 let mut txs = self
1037 .protocol_pool
1038 .get_pending_transactions_by_origin(origin);
1039 txs.extend(
1040 self.aa_2d_pool
1041 .read()
1042 .get_pending_transactions_by_origin_iter(origin),
1043 );
1044 txs
1045 }
1046
1047 fn unique_senders(&self) -> AddressSet {
1048 let mut senders = self.protocol_pool.unique_senders();
1049 senders.extend(self.aa_2d_pool.read().senders_iter().copied());
1050 senders
1051 }
1052
1053 fn get_blob(
1054 &self,
1055 tx_hash: B256,
1056 ) -> Result<
1057 Option<Arc<alloy_eips::eip7594::BlobTransactionSidecarVariant>>,
1058 reth_transaction_pool::blobstore::BlobStoreError,
1059 > {
1060 self.protocol_pool.get_blob(tx_hash)
1061 }
1062
1063 fn get_all_blobs(
1064 &self,
1065 tx_hashes: Vec<B256>,
1066 ) -> Result<
1067 Vec<(
1068 B256,
1069 Arc<alloy_eips::eip7594::BlobTransactionSidecarVariant>,
1070 )>,
1071 reth_transaction_pool::blobstore::BlobStoreError,
1072 > {
1073 self.protocol_pool.get_all_blobs(tx_hashes)
1074 }
1075
1076 fn get_all_blobs_exact(
1077 &self,
1078 tx_hashes: Vec<B256>,
1079 ) -> Result<
1080 Vec<Arc<alloy_eips::eip7594::BlobTransactionSidecarVariant>>,
1081 reth_transaction_pool::blobstore::BlobStoreError,
1082 > {
1083 self.protocol_pool.get_all_blobs_exact(tx_hashes)
1084 }
1085
1086 fn get_blobs_for_versioned_hashes_v1(
1087 &self,
1088 versioned_hashes: &[B256],
1089 ) -> Result<
1090 Vec<Option<alloy_eips::eip4844::BlobAndProofV1>>,
1091 reth_transaction_pool::blobstore::BlobStoreError,
1092 > {
1093 self.protocol_pool
1094 .get_blobs_for_versioned_hashes_v1(versioned_hashes)
1095 }
1096
1097 fn get_blobs_for_versioned_hashes_v2(
1098 &self,
1099 versioned_hashes: &[B256],
1100 ) -> Result<
1101 Option<Vec<alloy_eips::eip4844::BlobAndProofV2>>,
1102 reth_transaction_pool::blobstore::BlobStoreError,
1103 > {
1104 self.protocol_pool
1105 .get_blobs_for_versioned_hashes_v2(versioned_hashes)
1106 }
1107
1108 fn get_blobs_for_versioned_hashes_v3(
1109 &self,
1110 versioned_hashes: &[B256],
1111 ) -> Result<
1112 Vec<Option<alloy_eips::eip4844::BlobAndProofV2>>,
1113 reth_transaction_pool::blobstore::BlobStoreError,
1114 > {
1115 self.protocol_pool
1116 .get_blobs_for_versioned_hashes_v3(versioned_hashes)
1117 }
1118
1119 fn get_blobs_for_versioned_hashes_v4(
1120 &self,
1121 versioned_hashes: &[B256],
1122 indices_bitarray: alloy_primitives::B128,
1123 ) -> Result<
1124 Vec<Option<alloy_eips::eip4844::BlobCellsAndProofsV1>>,
1125 reth_transaction_pool::blobstore::BlobStoreError,
1126 > {
1127 self.protocol_pool
1128 .get_blobs_for_versioned_hashes_v4(versioned_hashes, indices_bitarray)
1129 }
1130}
1131
1132impl<Client> TransactionPoolExt for TempoTransactionPool<Client>
1133where
1134 Client: StateProviderFactory + ChainSpecProvider<ChainSpec = TempoChainSpec> + 'static,
1135{
1136 type Block = Block;
1137
1138 fn set_block_info(&self, info: BlockInfo) {
1139 self.protocol_pool.set_block_info(info)
1140 }
1141
1142 fn on_canonical_state_change(&self, update: CanonicalStateUpdate<'_, Self::Block>) {
1143 self.protocol_pool.on_canonical_state_change(update)
1144 }
1145
1146 fn update_accounts(&self, accounts: Vec<ChangedAccount>) {
1147 self.protocol_pool.update_accounts(accounts)
1148 }
1149
1150 fn delete_blob(&self, tx: B256) {
1151 self.protocol_pool.delete_blob(tx)
1152 }
1153
1154 fn delete_blobs(&self, txs: Vec<B256>) {
1155 self.protocol_pool.delete_blobs(txs)
1156 }
1157
1158 fn cleanup_blobs(&self) {
1159 self.protocol_pool.cleanup_blobs()
1160 }
1161}
1162
1163pub(crate) fn exceeds_spending_limit(
1169 provider: &mut impl StateProvider,
1170 subject: &crate::transaction::KeychainSubject,
1171 fee_token_cost: alloy_primitives::U256,
1172 current_timestamp: u64,
1173 spec: TempoHardfork,
1174) -> bool {
1175 provider
1176 .with_read_only_storage_ctx(spec, || -> TempoPrecompileResult<bool> {
1177 let keychain = AccountKeychain::new();
1178 if !keychain.keys[subject.account][subject.key_id]
1179 .read()?
1180 .enforce_limits
1181 {
1182 return Ok(false);
1183 }
1184
1185 let remaining = keychain.effective_remaining_limit(
1186 subject.account,
1187 subject.key_id,
1188 subject.fee_token,
1189 current_timestamp,
1190 )?;
1191 Ok(fee_token_cost > remaining)
1192 })
1193 .unwrap_or_default()
1194}
1195
1196fn get_sender_policy_ids(
1204 provider: &mut impl StateProvider,
1205 fee_token: Address,
1206 spec: TempoHardfork,
1207 cache: &mut AddressMap<Vec<u64>>,
1208) -> Option<Vec<u64>> {
1209 if let Some(cached) = cache.get(&fee_token) {
1210 return Some(cached.clone());
1211 }
1212
1213 provider.with_read_only_storage_ctx(spec, || {
1214 let policy_id = TIP20Token::from_address(fee_token)
1215 .and_then(|t| t.transfer_policy_id())
1216 .ok()
1217 .filter(|&id| id != REJECT_ALL_POLICY_ID)?;
1218
1219 let mut ids = vec![policy_id];
1220
1221 let registry = TIP403Registry::new();
1223 if let Ok(data) = registry.policy_records[policy_id].base.read()
1224 && data.is_compound()
1225 && let Ok(compound) = registry.policy_records[policy_id].compound.read()
1226 && compound.sender_policy_id != REJECT_ALL_POLICY_ID
1227 {
1228 ids.push(compound.sender_policy_id);
1229 }
1230
1231 cache.insert(fee_token, ids.clone());
1234 Some(ids)
1235 })
1236}
1237
1238fn get_recipient_policy_ids(
1248 provider: &mut impl StateProvider,
1249 fee_token: Address,
1250 spec: TempoHardfork,
1251) -> Option<Vec<u64>> {
1252 provider.with_read_only_storage_ctx(spec, || {
1253 let policy_id = TIP20Token::from_address(fee_token)
1254 .and_then(|t| t.transfer_policy_id())
1255 .ok()
1256 .filter(|&id| id != REJECT_ALL_POLICY_ID)?;
1257
1258 let mut ids = vec![policy_id];
1259
1260 let registry = TIP403Registry::new();
1261 if let Ok(data) = registry.policy_records[policy_id].base.read()
1262 && data.is_compound()
1263 && let Ok(compound) = registry.policy_records[policy_id].compound.read()
1264 && compound.recipient_policy_id != REJECT_ALL_POLICY_ID
1265 {
1266 ids.push(compound.recipient_policy_id);
1267 }
1268
1269 Some(ids)
1270 })
1271}
1272
1273#[cfg(test)]
1274mod tests {
1275 use super::*;
1276 use crate::{test_utils::MockProviderStorageExt, transaction::KeychainSubject};
1277 use alloy_consensus::Header;
1278 use alloy_primitives::{U256, address, uint};
1279 use alloy_signer::SignerSync;
1280 use alloy_signer_local::PrivateKeySigner;
1281 use reth_primitives_traits::Recovered;
1282 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1283 use reth_storage_api::StateProviderFactory;
1284 use reth_transaction_pool::{
1285 PoolConfig, TransactionOrigin, TransactionPool, TransactionValidationTaskExecutor,
1286 blobstore::InMemoryBlobStore,
1287 validate::{EthTransactionValidatorBuilder, ValidTransaction},
1288 };
1289 use tempo_chainspec::{
1290 hardfork::TempoHardfork,
1291 spec::{MODERATO, TEMPO_T1_TX_GAS_LIMIT_CAP},
1292 };
1293 use tempo_contracts::precompiles::ITIP403Registry;
1294 use tempo_evm::TempoEvmConfig;
1295 use tempo_precompiles::{
1296 PATH_USD_ADDRESS,
1297 account_keychain::{AccountKeychain, AuthorizedKey, SpendingLimitState},
1298 tip20::slots as tip20_slots,
1299 tip403_registry::{CompoundPolicyData, PolicyData, TIP403Registry},
1300 };
1301 use tempo_primitives::{Block, TempoHeader, TempoPrimitives, TempoTxEnvelope};
1302
1303 fn provider_with_spending_limit(
1304 account: Address,
1305 key_id: Address,
1306 fee_token: Address,
1307 remaining_limit: alloy_primitives::U256,
1308 ) -> Box<dyn reth_storage_api::StateProvider> {
1309 provider_with_spending_limit_state(
1310 account,
1311 key_id,
1312 fee_token,
1313 SpendingLimitState {
1314 remaining: remaining_limit,
1315 ..Default::default()
1316 },
1317 TempoHardfork::default(),
1318 )
1319 }
1320
1321 fn provider_with_spending_limit_state(
1322 account: Address,
1323 key_id: Address,
1324 fee_token: Address,
1325 limit_state: SpendingLimitState,
1326 setup_spec: TempoHardfork,
1327 ) -> Box<dyn reth_storage_api::StateProvider> {
1328 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
1329 tempo_chainspec::spec::MODERATO.clone(),
1330 ));
1331
1332 provider
1334 .setup_storage(setup_spec, || {
1335 let mut keychain = AccountKeychain::new();
1336 keychain.keys[account][key_id].write(AuthorizedKey {
1337 signature_type: 0,
1338 expiry: u64::MAX,
1339 enforce_limits: true,
1340 is_revoked: false,
1341 })?;
1342 let limit_key = AccountKeychain::spending_limit_key(account, key_id);
1343 keychain.spending_limits[limit_key][fee_token].write(limit_state)?;
1344 Ok::<(), tempo_precompiles::error::TempoPrecompileError>(())
1345 })
1346 .unwrap();
1347
1348 provider.latest().unwrap()
1349 }
1350
1351 fn set_fee_token_balance(
1352 provider: &MockEthProvider<TempoPrimitives, TempoChainSpec>,
1353 fee_token: Address,
1354 account: Address,
1355 balance: U256,
1356 ) {
1357 let usd_currency_value =
1358 uint!(0x5553440000000000000000000000000000000000000000000000000000000006_U256);
1359 let transfer_policy_id_packed =
1360 uint!(0x0000000000000000000000010000000000000000000000000000000000000000_U256);
1361 let balance_slot = TIP20Token::from_address(fee_token)
1362 .expect("fee token must be a valid TIP20 token")
1363 .balances[account]
1364 .slot();
1365
1366 provider.add_account(
1367 fee_token,
1368 ExtendedAccount::new(0, U256::ZERO).extend_storage([
1369 (tip20_slots::CURRENCY.into(), usd_currency_value),
1370 (
1371 tip20_slots::TRANSFER_POLICY_ID.into(),
1372 transfer_policy_id_packed,
1373 ),
1374 (balance_slot.into(), balance),
1375 ]),
1376 );
1377 }
1378
1379 #[tokio::test]
1380 async fn evicts_sponsored_transactions_when_fee_payer_becomes_insolvent() {
1381 let fee_payer_signer = PrivateKeySigner::random();
1382 let fee_payer = fee_payer_signer.address();
1383 let sender = Address::random();
1384
1385 let envelope = crate::test_utils::TxBuilder::aa(sender)
1386 .fee_token(PATH_USD_ADDRESS)
1387 .build()
1388 .inner()
1389 .clone()
1390 .into_inner();
1391 let TempoTxEnvelope::AA(mut signed) = envelope else {
1392 panic!("expected AA transaction");
1393 };
1394 let fee_payer_hash = signed.tx().fee_payer_signature_hash(sender);
1395 signed.tx_mut().fee_payer_signature = Some(
1396 fee_payer_signer
1397 .sign_hash_sync(&fee_payer_hash)
1398 .expect("fee payer signing should succeed"),
1399 );
1400 let pooled = TempoPooledTransaction::new(Recovered::new_unchecked(
1401 TempoTxEnvelope::AA(signed),
1402 sender,
1403 ));
1404
1405 let provider = MockEthProvider::<TempoPrimitives>::new()
1406 .with_chain_spec(std::sync::Arc::unwrap_or_clone(MODERATO.clone()));
1407 provider.add_account(sender, ExtendedAccount::new(pooled.nonce(), *pooled.cost()));
1408 provider.add_block(
1409 B256::random(),
1410 Block {
1411 header: TempoHeader {
1412 inner: Header {
1413 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
1414 ..Default::default()
1415 },
1416 ..Default::default()
1417 },
1418 ..Default::default()
1419 },
1420 );
1421
1422 let initial_balance = pooled.fee_token_cost() + U256::from(1_u64);
1423 set_fee_token_balance(&provider, PATH_USD_ADDRESS, fee_payer, initial_balance);
1424
1425 let inner =
1426 EthTransactionValidatorBuilder::new(provider.clone(), TempoEvmConfig::mainnet())
1427 .disable_balance_check()
1428 .build(InMemoryBlobStore::default());
1429 let amm_cache =
1430 AmmLiquidityCache::new(provider.clone()).expect("failed to setup AmmLiquidityCache");
1431 let validator = TempoTransactionValidator::new(
1432 inner,
1433 crate::validator::DEFAULT_AA_VALID_AFTER_MAX_SECS,
1434 crate::validator::DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
1435 amm_cache,
1436 );
1437
1438 let (executor, _task) = TransactionValidationTaskExecutor::new(validator);
1439 let protocol_pool = Pool::new(
1440 executor,
1441 CoinbaseTipOrdering::default(),
1442 InMemoryBlobStore::default(),
1443 PoolConfig::default(),
1444 );
1445 let pool = TempoTransactionPool::new(protocol_pool, AA2dPool::new(Default::default()));
1446
1447 pooled.set_resolved_fee_token(PATH_USD_ADDRESS);
1448 let validated = TransactionValidationOutcome::Valid {
1449 balance: *pooled.cost(),
1450 state_nonce: pooled.nonce(),
1451 bytecode_hash: None,
1452 transaction: ValidTransaction::new(pooled.clone(), None),
1453 propagate: true,
1454 authorities: None,
1455 };
1456 let add_result = pool.add_validated_transaction(TransactionOrigin::External, validated);
1457 assert!(
1458 add_result.is_ok(),
1459 "transaction should be admitted before sponsor drains balance: {add_result:?}"
1460 );
1461
1462 set_fee_token_balance(
1463 &provider,
1464 PATH_USD_ADDRESS,
1465 fee_payer,
1466 pooled.fee_token_cost() - U256::from(1_u64),
1467 );
1468
1469 let mut updates = crate::maintain::TempoPoolUpdates::new();
1470 updates
1471 .fee_balance_changes
1472 .entry(PATH_USD_ADDRESS)
1473 .or_default()
1474 .insert(fee_payer);
1475
1476 let evicted = pool.evict_invalidated_transactions(&updates);
1477 assert_eq!(evicted, vec![*pooled.hash()]);
1478 assert!(pool.get(pooled.hash()).is_none());
1479 }
1480
1481 #[test]
1485 fn compound_policy_sub_policy_matches_eviction_check() {
1486 let fee_token = address!("20C0000000000000000000000000000000000001");
1487 let compound_policy_id: u64 = 5;
1488 let sender_sub_policy: u64 = 3;
1489 let recipient_sub_policy: u64 = 4;
1490
1491 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
1492 tempo_chainspec::spec::MODERATO.clone(),
1493 ));
1494
1495 let transfer_policy_id_packed =
1497 U256::from(compound_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
1498 provider.add_account(
1499 fee_token,
1500 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
1501 tip20_slots::TRANSFER_POLICY_ID.into(),
1502 transfer_policy_id_packed,
1503 )]),
1504 );
1505
1506 provider
1508 .setup_storage(TempoHardfork::default(), || {
1509 let mut registry = TIP403Registry::new();
1510 registry.policy_records[compound_policy_id]
1511 .base
1512 .write(PolicyData {
1513 policy_type: ITIP403Registry::PolicyType::COMPOUND as u8,
1514 admin: Address::ZERO,
1515 })?;
1516 registry.policy_records[compound_policy_id]
1517 .compound
1518 .write(CompoundPolicyData {
1519 sender_policy_id: sender_sub_policy,
1520 recipient_policy_id: recipient_sub_policy,
1521 mint_recipient_policy_id: 0,
1522 })
1523 })
1524 .unwrap();
1525
1526 let mut state = provider.latest().unwrap();
1527 let mut cache: AddressMap<Vec<u64>> = AddressMap::default();
1528
1529 let ids =
1530 get_sender_policy_ids(&mut state, fee_token, TempoHardfork::default(), &mut cache)
1531 .expect("should resolve policy IDs");
1532
1533 assert!(
1534 ids.contains(&compound_policy_id),
1535 "should contain compound policy ID"
1536 );
1537 assert!(
1538 ids.contains(&sender_sub_policy),
1539 "should contain sender sub-policy"
1540 );
1541 }
1542
1543 #[test]
1546 fn compound_policy_sender_ids_exclude_recipient_sub_policy() {
1547 let fee_token = address!("20C0000000000000000000000000000000000001");
1548 let compound_policy_id: u64 = 5;
1549 let sender_sub_policy: u64 = 3;
1550 let recipient_sub_policy: u64 = 4;
1551
1552 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
1553 tempo_chainspec::spec::MODERATO.clone(),
1554 ));
1555
1556 let transfer_policy_id_packed =
1557 U256::from(compound_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
1558 provider.add_account(
1559 fee_token,
1560 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
1561 tip20_slots::TRANSFER_POLICY_ID.into(),
1562 transfer_policy_id_packed,
1563 )]),
1564 );
1565
1566 provider
1567 .setup_storage(TempoHardfork::default(), || {
1568 let mut registry = TIP403Registry::new();
1569 registry.policy_records[compound_policy_id]
1570 .base
1571 .write(PolicyData {
1572 policy_type: ITIP403Registry::PolicyType::COMPOUND as u8,
1573 admin: Address::ZERO,
1574 })?;
1575 registry.policy_records[compound_policy_id]
1576 .compound
1577 .write(CompoundPolicyData {
1578 sender_policy_id: sender_sub_policy,
1579 recipient_policy_id: recipient_sub_policy,
1580 mint_recipient_policy_id: 0,
1581 })
1582 })
1583 .unwrap();
1584
1585 let mut state = provider.latest().unwrap();
1586 let mut cache: AddressMap<Vec<u64>> = AddressMap::default();
1587
1588 let ids =
1589 get_sender_policy_ids(&mut state, fee_token, TempoHardfork::default(), &mut cache)
1590 .expect("should resolve policy IDs");
1591
1592 assert!(ids.contains(&compound_policy_id));
1593 assert!(ids.contains(&sender_sub_policy));
1594 assert!(
1595 !ids.contains(&recipient_sub_policy),
1596 "sender policy IDs should not contain recipient_sub_policy"
1597 );
1598 }
1599
1600 #[test]
1603 fn compound_policy_excludes_mint_recipient() {
1604 let fee_token = address!("20C0000000000000000000000000000000000001");
1605 let compound_policy_id: u64 = 5;
1606 let sender_sub: u64 = 3;
1607 let recipient_sub: u64 = 4;
1608 let mint_recipient_sub: u64 = 6;
1609
1610 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
1611 tempo_chainspec::spec::MODERATO.clone(),
1612 ));
1613
1614 let transfer_policy_id_packed =
1615 U256::from(compound_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
1616 provider.add_account(
1617 fee_token,
1618 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
1619 tip20_slots::TRANSFER_POLICY_ID.into(),
1620 transfer_policy_id_packed,
1621 )]),
1622 );
1623
1624 provider
1625 .setup_storage(TempoHardfork::default(), || {
1626 let mut registry = TIP403Registry::new();
1627 registry.policy_records[compound_policy_id]
1628 .base
1629 .write(PolicyData {
1630 policy_type: ITIP403Registry::PolicyType::COMPOUND as u8,
1631 admin: Address::ZERO,
1632 })?;
1633 registry.policy_records[compound_policy_id]
1634 .compound
1635 .write(CompoundPolicyData {
1636 sender_policy_id: sender_sub,
1637 recipient_policy_id: recipient_sub,
1638 mint_recipient_policy_id: mint_recipient_sub,
1639 })
1640 })
1641 .unwrap();
1642
1643 let mut state = provider.latest().unwrap();
1644 let mut cache: AddressMap<Vec<u64>> = AddressMap::default();
1645
1646 let ids =
1647 get_sender_policy_ids(&mut state, fee_token, TempoHardfork::default(), &mut cache)
1648 .expect("should resolve policy IDs");
1649
1650 assert!(
1651 !ids.contains(&mint_recipient_sub),
1652 "mint_recipient must be excluded from sender policy IDs"
1653 );
1654 }
1655
1656 #[test]
1658 fn recipient_policy_ids_includes_recipient_sub_policy() {
1659 let fee_token = address!("20C0000000000000000000000000000000000001");
1660 let compound_policy_id: u64 = 5;
1661 let sender_sub: u64 = 3;
1662 let recipient_sub: u64 = 4;
1663
1664 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
1665 tempo_chainspec::spec::MODERATO.clone(),
1666 ));
1667
1668 let transfer_policy_id_packed =
1669 U256::from(compound_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
1670 provider.add_account(
1671 fee_token,
1672 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
1673 tip20_slots::TRANSFER_POLICY_ID.into(),
1674 transfer_policy_id_packed,
1675 )]),
1676 );
1677
1678 provider
1679 .setup_storage(TempoHardfork::default(), || {
1680 let mut registry = TIP403Registry::new();
1681 registry.policy_records[compound_policy_id]
1682 .base
1683 .write(PolicyData {
1684 policy_type: ITIP403Registry::PolicyType::COMPOUND as u8,
1685 admin: Address::ZERO,
1686 })?;
1687 registry.policy_records[compound_policy_id]
1688 .compound
1689 .write(CompoundPolicyData {
1690 sender_policy_id: sender_sub,
1691 recipient_policy_id: recipient_sub,
1692 mint_recipient_policy_id: 0,
1693 })
1694 })
1695 .unwrap();
1696
1697 let mut state = provider.latest().unwrap();
1698 let ids = get_recipient_policy_ids(&mut state, fee_token, TempoHardfork::default())
1699 .expect("should resolve policy IDs");
1700
1701 assert!(
1702 ids.contains(&compound_policy_id),
1703 "should contain compound policy ID"
1704 );
1705 assert!(
1706 ids.contains(&recipient_sub),
1707 "should contain recipient sub-policy"
1708 );
1709 assert!(
1710 !ids.contains(&sender_sub),
1711 "recipient policy IDs should not contain sender sub-policy"
1712 );
1713 }
1714
1715 #[test]
1717 fn recipient_policy_ids_simple_policy() {
1718 let fee_token = address!("20C0000000000000000000000000000000000001");
1719 let simple_policy_id: u64 = 7;
1720
1721 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
1722 tempo_chainspec::spec::MODERATO.clone(),
1723 ));
1724
1725 let transfer_policy_id_packed =
1726 U256::from(simple_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
1727 provider.add_account(
1728 fee_token,
1729 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
1730 tip20_slots::TRANSFER_POLICY_ID.into(),
1731 transfer_policy_id_packed,
1732 )]),
1733 );
1734
1735 provider
1736 .setup_storage(TempoHardfork::default(), || {
1737 let mut registry = TIP403Registry::new();
1738 registry.policy_records[simple_policy_id]
1739 .base
1740 .write(PolicyData {
1741 policy_type: ITIP403Registry::PolicyType::BLACKLIST as u8,
1742 admin: Address::ZERO,
1743 })
1744 })
1745 .unwrap();
1746
1747 let mut state = provider.latest().unwrap();
1748 let ids = get_recipient_policy_ids(&mut state, fee_token, TempoHardfork::default())
1749 .expect("should resolve policy IDs");
1750
1751 assert_eq!(ids, vec![simple_policy_id]);
1752 }
1753
1754 #[test]
1755 fn exceeds_spending_limit_returns_true_when_cost_exceeds_remaining() {
1756 let account = Address::random();
1757 let key_id = Address::random();
1758 let fee_token = Address::random();
1759 let subject = KeychainSubject {
1760 account,
1761 key_id,
1762 fee_token,
1763 };
1764
1765 let mut state = provider_with_spending_limit(
1766 account,
1767 key_id,
1768 fee_token,
1769 alloy_primitives::U256::from(100),
1770 );
1771
1772 assert!(exceeds_spending_limit(
1773 &mut state,
1774 &subject,
1775 alloy_primitives::U256::from(200),
1776 0,
1777 TempoHardfork::default(),
1778 ));
1779 }
1780
1781 #[test]
1782 fn exceeds_spending_limit_returns_false_when_cost_within_limit() {
1783 let account = Address::random();
1784 let key_id = Address::random();
1785 let fee_token = Address::random();
1786 let subject = KeychainSubject {
1787 account,
1788 key_id,
1789 fee_token,
1790 };
1791
1792 let mut state = provider_with_spending_limit(
1793 account,
1794 key_id,
1795 fee_token,
1796 alloy_primitives::U256::from(500),
1797 );
1798
1799 assert!(!exceeds_spending_limit(
1800 &mut state,
1801 &subject,
1802 alloy_primitives::U256::from(200),
1803 0,
1804 TempoHardfork::default(),
1805 ));
1806 }
1807
1808 #[test]
1809 fn exceeds_spending_limit_returns_true_when_no_limit_set() {
1810 let account = Address::random();
1811 let key_id = Address::random();
1812 let fee_token = Address::random();
1813 let subject = KeychainSubject {
1814 account,
1815 key_id,
1816 fee_token,
1817 };
1818
1819 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
1821 tempo_chainspec::spec::MODERATO.clone(),
1822 ));
1823 provider
1824 .setup_storage(TempoHardfork::default(), || {
1825 AccountKeychain::new().keys[account][key_id].write(AuthorizedKey {
1826 signature_type: 0,
1827 expiry: u64::MAX,
1828 enforce_limits: true,
1829 is_revoked: false,
1830 })
1831 })
1832 .unwrap();
1833
1834 assert!(exceeds_spending_limit(
1835 &mut provider.latest().unwrap(),
1836 &subject,
1837 alloy_primitives::U256::from(1),
1838 0,
1839 TempoHardfork::default(),
1840 ));
1841 }
1842
1843 #[test]
1844 fn exceeds_spending_limit_returns_false_when_limits_not_enforced() {
1845 let account = Address::random();
1846 let key_id = Address::random();
1847 let fee_token = Address::random();
1848 let subject = KeychainSubject {
1849 account,
1850 key_id,
1851 fee_token,
1852 };
1853
1854 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
1856 tempo_chainspec::spec::MODERATO.clone(),
1857 ));
1858 provider
1859 .setup_storage(TempoHardfork::default(), || {
1860 AccountKeychain::new().keys[account][key_id].write(AuthorizedKey {
1861 signature_type: 0,
1862 expiry: u64::MAX,
1863 enforce_limits: false,
1864 is_revoked: false,
1865 })
1866 })
1867 .unwrap();
1868
1869 assert!(!exceeds_spending_limit(
1870 &mut provider.latest().unwrap(),
1871 &subject,
1872 alloy_primitives::U256::from(1),
1873 0,
1874 TempoHardfork::default(),
1875 ));
1876 }
1877
1878 #[test]
1879 fn exceeds_spending_limit_uses_period_reset_after_rollover() {
1880 let account = Address::random();
1881 let key_id = Address::random();
1882 let fee_token = Address::random();
1883 let subject = KeychainSubject {
1884 account,
1885 key_id,
1886 fee_token,
1887 };
1888
1889 let mut state = provider_with_spending_limit_state(
1890 account,
1891 key_id,
1892 fee_token,
1893 SpendingLimitState {
1894 remaining: alloy_primitives::U256::ZERO,
1895 max: 100,
1896 period: 60,
1897 period_end: 10,
1898 },
1899 TempoHardfork::T3,
1900 );
1901
1902 assert!(!exceeds_spending_limit(
1903 &mut state,
1904 &subject,
1905 alloy_primitives::U256::from(50),
1906 10,
1907 TempoHardfork::T3,
1908 ));
1909 assert!(exceeds_spending_limit(
1910 &mut state,
1911 &subject,
1912 alloy_primitives::U256::from(150),
1913 10,
1914 TempoHardfork::T3,
1915 ));
1916 }
1917}