1use crate::{
2 amm::AmmLiquidityCache,
3 transaction::{TempoPoolTransactionError, TempoPooledTransaction},
4};
5use alloy_consensus::Transaction;
6
7use alloy_primitives::{Address, U256};
8use reth_chainspec::{ChainSpecProvider, EthChainSpec};
9use reth_primitives_traits::{
10 Block, GotExpected, SealedBlock, transaction::error::InvalidTransactionError,
11};
12use reth_storage_api::{StateProvider, StateProviderFactory, errors::ProviderError};
13use reth_transaction_pool::{
14 EthTransactionValidator, PoolTransaction, TransactionOrigin, TransactionValidationOutcome,
15 TransactionValidator, error::InvalidPoolTransactionError,
16};
17use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardforks};
18use tempo_precompiles::{
19 ACCOUNT_KEYCHAIN_ADDRESS, NONCE_PRECOMPILE_ADDRESS,
20 account_keychain::{AccountKeychain, AuthorizedKey},
21};
22use tempo_primitives::{subblock::has_sub_block_nonce_key_prefix, transaction::TempoTransaction};
23use tempo_revm::TempoStateAccess;
24
25const AA_VALID_BEFORE_MIN_SECS: u64 = 3;
27
28#[derive(Debug)]
30pub struct TempoTransactionValidator<Client> {
31 pub(crate) inner: EthTransactionValidator<Client, TempoPooledTransaction>,
33 pub(crate) aa_valid_after_max_secs: u64,
35 pub(crate) amm_liquidity_cache: AmmLiquidityCache,
37}
38
39impl<Client> TempoTransactionValidator<Client>
40where
41 Client: ChainSpecProvider<ChainSpec = TempoChainSpec> + StateProviderFactory,
42{
43 pub fn new(
44 inner: EthTransactionValidator<Client, TempoPooledTransaction>,
45 aa_valid_after_max_secs: u64,
46 amm_liquidity_cache: AmmLiquidityCache,
47 ) -> Self {
48 Self {
49 inner,
50 aa_valid_after_max_secs,
51 amm_liquidity_cache,
52 }
53 }
54
55 pub fn amm_liquidity_cache(&self) -> AmmLiquidityCache {
57 self.amm_liquidity_cache.clone()
58 }
59
60 pub fn client(&self) -> &Client {
62 self.inner.client()
63 }
64
65 fn validate_against_keychain(
72 &self,
73 transaction: &TempoPooledTransaction,
74 state_provider: &impl StateProvider,
75 ) -> Result<Result<(), &'static str>, ProviderError> {
76 let Some(tx) = transaction.inner().as_aa() else {
77 return Ok(Ok(()));
78 };
79
80 let is_allegretto = self
81 .inner
82 .chain_spec()
83 .is_allegretto_active_at_timestamp(self.inner.fork_tracker().tip_timestamp());
84
85 let auth = tx.tx().key_authorization.as_ref();
86
87 if (auth.is_some() || tx.signature().is_keychain()) && !is_allegretto {
88 return Ok(Err(
89 "keychain operations are only supported after Allegretto",
90 ));
91 }
92
93 if let Some(auth) = auth {
95 if !auth
97 .recover_signer()
98 .is_ok_and(|signer| signer == transaction.sender())
99 {
100 return Ok(Err("Invalid KeyAuthorization signature"));
101 }
102
103 let chain_id = self.inner.chain_spec().chain_id();
105 if auth.chain_id != 0 && auth.chain_id != chain_id {
106 return Ok(Err(
107 "KeyAuthorization chain_id does not match current chain",
108 ));
109 }
110 }
111
112 let Some(sig) = tx.signature().as_keychain() else {
113 return Ok(Ok(()));
114 };
115
116 if sig.user_address != transaction.sender() {
118 return Ok(Err("Keychain signature user_address does not match sender"));
119 }
120
121 let Ok(key_id) = sig.key_id(&tx.signature_hash()) else {
123 return Ok(Err(
124 "Failed to recover access key ID from Keychain signature",
125 ));
126 };
127
128 if let Some(auth) = auth {
130 if auth.key_id != key_id {
131 return Ok(Err(
132 "KeyAuthorization key_id does not match Keychain signature key_id",
133 ));
134 }
135
136 return Ok(Ok(()));
138 }
139
140 let storage_slot = AccountKeychain::new()
142 .keys
143 .at(transaction.sender())
144 .at(key_id)
145 .base_slot();
146
147 let slot_value = state_provider
149 .storage(ACCOUNT_KEYCHAIN_ADDRESS, storage_slot.into())?
150 .unwrap_or(U256::ZERO);
151
152 let authorized_key = AuthorizedKey::decode_from_slot(slot_value);
154
155 if authorized_key.is_revoked {
157 return Ok(Err("access key has been revoked"));
158 }
159
160 if authorized_key.expiry == 0 {
162 return Ok(Err("access key does not exist"));
163 }
164
165 Ok(Ok(()))
167 }
168
169 fn ensure_valid_conditionals(
171 &self,
172 tx: &TempoTransaction,
173 ) -> Result<(), TempoPoolTransactionError> {
174 if let Some(valid_before) = tx.valid_before {
176 let current_time = self.inner.fork_tracker().tip_timestamp();
178 let min_allowed = current_time.saturating_add(AA_VALID_BEFORE_MIN_SECS);
179 if valid_before <= min_allowed {
180 return Err(TempoPoolTransactionError::InvalidValidBefore {
181 valid_before,
182 min_allowed,
183 });
184 }
185 }
186
187 if let Some(valid_after) = tx.valid_after {
189 let current_time = std::time::SystemTime::now()
191 .duration_since(std::time::UNIX_EPOCH)
192 .map(|d| d.as_secs())
193 .unwrap_or(0);
194 let max_allowed = current_time.saturating_add(self.aa_valid_after_max_secs);
195 if valid_after > max_allowed {
196 return Err(TempoPoolTransactionError::InvalidValidAfter {
197 valid_after,
198 max_allowed,
199 });
200 }
201 }
202
203 Ok(())
204 }
205
206 fn validate_one(
207 &self,
208 origin: TransactionOrigin,
209 transaction: TempoPooledTransaction,
210 mut state_provider: impl StateProvider,
211 ) -> TransactionValidationOutcome<TempoPooledTransaction> {
212 if transaction.inner().is_system_tx() {
214 return TransactionValidationOutcome::Error(
215 *transaction.hash(),
216 InvalidTransactionError::TxTypeNotSupported.into(),
217 );
218 }
219
220 match self.validate_against_keychain(&transaction, &state_provider) {
222 Ok(Ok(())) => {}
223 Ok(Err(reason)) => {
224 return TransactionValidationOutcome::Invalid(
225 transaction,
226 InvalidPoolTransactionError::other(TempoPoolTransactionError::Keychain(reason)),
227 );
228 }
229 Err(err) => {
230 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
231 }
232 }
233
234 if !transaction.inner().value().is_zero() {
238 return TransactionValidationOutcome::Invalid(
239 transaction,
240 InvalidPoolTransactionError::other(TempoPoolTransactionError::NonZeroValue),
241 );
242 }
243
244 if let Some(tx) = transaction.inner().as_aa()
246 && let Err(err) = self.ensure_valid_conditionals(tx.tx())
247 {
248 return TransactionValidationOutcome::Invalid(
249 transaction,
250 InvalidPoolTransactionError::other(err),
251 );
252 }
253
254 let fee_payer = match transaction.inner().fee_payer(transaction.sender()) {
255 Ok(fee_payer) => fee_payer,
256 Err(err) => {
257 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
258 }
259 };
260
261 let spec = self
262 .inner
263 .chain_spec()
264 .tempo_hardfork_at(self.inner.fork_tracker().tip_timestamp());
265 let fee_token =
266 match state_provider.get_fee_token(transaction.inner(), Address::ZERO, fee_payer, spec)
267 {
268 Ok(fee_token) => fee_token,
269 Err(err) => {
270 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
271 }
272 };
273
274 match state_provider.is_valid_fee_token(fee_token, spec) {
276 Ok(valid) => {
277 if !valid {
278 return TransactionValidationOutcome::Invalid(
279 transaction,
280 InvalidPoolTransactionError::other(
281 TempoPoolTransactionError::InvalidFeeToken(fee_token),
282 ),
283 );
284 }
285 }
286 Err(err) => {
287 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
288 }
289 }
290
291 match state_provider.can_fee_payer_transfer(fee_token, fee_payer) {
293 Ok(valid) => {
294 if !valid {
295 return TransactionValidationOutcome::Invalid(
296 transaction,
297 InvalidPoolTransactionError::other(
298 TempoPoolTransactionError::BlackListedFeePayer {
299 fee_token,
300 fee_payer,
301 },
302 ),
303 );
304 }
305 }
306 Err(err) => {
307 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
308 }
309 }
310
311 let balance = match state_provider.get_token_balance(fee_token, fee_payer) {
312 Ok(balance) => balance,
313 Err(err) => {
314 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
315 }
316 };
317
318 let cost = transaction.fee_token_cost();
320 if balance < cost {
321 return TransactionValidationOutcome::Invalid(
322 transaction,
323 InvalidTransactionError::InsufficientFunds(
324 GotExpected {
325 got: balance,
326 expected: cost,
327 }
328 .into(),
329 )
330 .into(),
331 );
332 }
333
334 match self
335 .amm_liquidity_cache
336 .has_enough_liquidity(fee_token, cost, &state_provider)
337 {
338 Ok(true) => {}
339 Ok(false) => {
340 return TransactionValidationOutcome::Invalid(
341 transaction,
342 InvalidPoolTransactionError::other(
343 TempoPoolTransactionError::InsufficientLiquidity(fee_token),
344 ),
345 );
346 }
347 Err(err) => {
348 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
349 }
350 }
351
352 match self
353 .inner
354 .validate_one_with_state_provider(origin, transaction, &state_provider)
355 {
356 TransactionValidationOutcome::Valid {
357 balance,
358 mut state_nonce,
359 bytecode_hash,
360 transaction,
361 propagate,
362 authorities,
363 } => {
364 if let Some(nonce_key) = transaction.transaction().nonce_key()
367 && !nonce_key.is_zero()
368 {
369 if has_sub_block_nonce_key_prefix(&nonce_key) {
371 return TransactionValidationOutcome::Invalid(
372 transaction.into_transaction(),
373 InvalidPoolTransactionError::other(
374 TempoPoolTransactionError::SubblockNonceKey,
375 ),
376 );
377 }
378
379 state_nonce = match state_provider.storage(
381 NONCE_PRECOMPILE_ADDRESS,
382 transaction.transaction().nonce_key_slot().unwrap().into(),
383 ) {
384 Ok(nonce) => nonce.unwrap_or_default().saturating_to(),
385 Err(err) => {
386 return TransactionValidationOutcome::Error(
387 *transaction.hash(),
388 Box::new(err),
389 );
390 }
391 };
392 let tx_nonce = transaction.nonce();
393 if tx_nonce < state_nonce {
394 return TransactionValidationOutcome::Invalid(
395 transaction.into_transaction(),
396 InvalidTransactionError::NonceNotConsistent {
397 tx: tx_nonce,
398 state: state_nonce,
399 }
400 .into(),
401 );
402 }
403 }
404
405 transaction.transaction().prepare_tx_env();
407
408 TransactionValidationOutcome::Valid {
409 balance,
410 state_nonce,
411 bytecode_hash,
412 transaction,
413 propagate,
414 authorities,
415 }
416 }
417 outcome => outcome,
418 }
419 }
420}
421
422impl<Client> TransactionValidator for TempoTransactionValidator<Client>
423where
424 Client: ChainSpecProvider<ChainSpec = TempoChainSpec> + StateProviderFactory,
425{
426 type Transaction = TempoPooledTransaction;
427
428 async fn validate_transaction(
429 &self,
430 origin: TransactionOrigin,
431 transaction: Self::Transaction,
432 ) -> TransactionValidationOutcome<Self::Transaction> {
433 let state_provider = match self.inner.client().latest() {
434 Ok(provider) => provider,
435 Err(err) => {
436 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err));
437 }
438 };
439
440 self.validate_one(origin, transaction, state_provider)
441 }
442
443 async fn validate_transactions(
444 &self,
445 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
446 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
447 let state_provider = match self.inner.client().latest() {
448 Ok(provider) => provider,
449 Err(err) => {
450 return transactions
451 .into_iter()
452 .map(|(_, tx)| {
453 TransactionValidationOutcome::Error(*tx.hash(), Box::new(err.clone()))
454 })
455 .collect();
456 }
457 };
458
459 transactions
460 .into_iter()
461 .map(|(origin, tx)| self.validate_one(origin, tx, &state_provider))
462 .collect()
463 }
464
465 async fn validate_transactions_with_origin(
466 &self,
467 origin: TransactionOrigin,
468 transactions: impl IntoIterator<Item = Self::Transaction> + Send,
469 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
470 let state_provider = match self.inner.client().latest() {
471 Ok(provider) => provider,
472 Err(err) => {
473 return transactions
474 .into_iter()
475 .map(|tx| {
476 TransactionValidationOutcome::Error(*tx.hash(), Box::new(err.clone()))
477 })
478 .collect();
479 }
480 };
481
482 transactions
483 .into_iter()
484 .map(|tx| self.validate_one(origin, tx, &state_provider))
485 .collect()
486 }
487
488 fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>)
489 where
490 B: Block,
491 {
492 self.inner.on_new_head_block(new_tip_block)
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499 use alloy_consensus::{Block, Transaction};
500 use alloy_eips::Decodable2718;
501 use alloy_primitives::{B256, U256, hex};
502 use reth_primitives_traits::SignedTransaction;
503 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
504 use reth_transaction_pool::{
505 PoolTransaction, blobstore::InMemoryBlobStore, validate::EthTransactionValidatorBuilder,
506 };
507 use std::sync::Arc;
508 use tempo_chainspec::spec::ANDANTINO;
509 use tempo_precompiles::tip403_registry::TIP403Registry;
510 use tempo_primitives::TempoTxEnvelope;
511
512 fn create_mock_block(timestamp: u64) -> SealedBlock<reth_ethereum_primitives::Block> {
514 use alloy_consensus::Header;
515 let header = Header {
516 timestamp,
517 ..Default::default()
518 };
519 let block = reth_ethereum_primitives::Block {
520 header,
521 body: Default::default(),
522 };
523 SealedBlock::seal_slow(block)
524 }
525
526 fn get_transaction(with_value: Option<U256>) -> TempoPooledTransaction {
527 let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
528
529 let data = hex::decode(raw).unwrap();
530 let mut tx = TempoTxEnvelope::decode_2718(&mut data.as_ref()).unwrap();
531
532 if let Some(value) = with_value {
533 match &mut tx {
534 TempoTxEnvelope::Legacy(tx) => tx.tx_mut().value = value,
535 TempoTxEnvelope::Eip2930(tx) => tx.tx_mut().value = value,
536 TempoTxEnvelope::Eip1559(tx) => tx.tx_mut().value = value,
537 TempoTxEnvelope::Eip7702(tx) => tx.tx_mut().value = value,
538 TempoTxEnvelope::AA(tx) => {
540 if let Some(first_call) = tx.tx_mut().calls.first_mut() {
541 first_call.value = value;
542 }
543 }
544 TempoTxEnvelope::FeeToken(tx) => tx.tx_mut().value = value,
545 }
546 }
547
548 TempoPooledTransaction::new(tx.try_into_recovered().unwrap())
549 }
550
551 fn create_aa_transaction(
554 valid_after: Option<u64>,
555 valid_before: Option<u64>,
556 ) -> TempoPooledTransaction {
557 use alloy_primitives::{Signature, TxKind, address};
558 use tempo_primitives::transaction::{
559 TempoTransaction,
560 tempo_transaction::Call,
561 tt_signature::{PrimitiveSignature, TempoSignature},
562 tt_signed::AASigned,
563 };
564
565 let tx_aa = TempoTransaction {
566 chain_id: 1,
567 max_priority_fee_per_gas: 1_000_000_000,
568 max_fee_per_gas: 2_000_000_000,
569 gas_limit: 100_000,
570 calls: vec![Call {
571 to: TxKind::Call(address!("0000000000000000000000000000000000000001")),
572 value: U256::ZERO,
573 input: alloy_primitives::Bytes::new(),
574 }],
575 nonce_key: U256::ZERO,
576 nonce: 0,
577 fee_token: Some(address!("0000000000000000000000000000000000000002")),
578 fee_payer_signature: None,
579 valid_after,
580 valid_before,
581 access_list: Default::default(),
582 tempo_authorization_list: vec![],
583 key_authorization: None,
584 };
585
586 let signed_tx = AASigned::new_unhashed(
587 tx_aa,
588 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())),
589 );
590 let envelope: TempoTxEnvelope = signed_tx.into();
591 let recovered = envelope.try_into_recovered().unwrap();
592 TempoPooledTransaction::new(recovered)
593 }
594
595 fn setup_validator(
597 transaction: &TempoPooledTransaction,
598 tip_timestamp: u64,
599 ) -> TempoTransactionValidator<
600 MockEthProvider<reth_ethereum_primitives::EthPrimitives, TempoChainSpec>,
601 > {
602 let provider =
603 MockEthProvider::default().with_chain_spec(Arc::unwrap_or_clone(ANDANTINO.clone()));
604 provider.add_account(
605 transaction.sender(),
606 ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
607 );
608 provider.add_block(B256::random(), Default::default());
609
610 let inner = EthTransactionValidatorBuilder::new(provider.clone())
611 .disable_balance_check()
612 .build(InMemoryBlobStore::default());
613 let amm_cache =
614 AmmLiquidityCache::new(provider).expect("failed to setup AmmLiquidityCache");
615 let validator = TempoTransactionValidator::new(inner, 3600, amm_cache);
616
617 let mock_block = create_mock_block(tip_timestamp);
619 validator.on_new_head_block(&mock_block);
620
621 validator
622 }
623
624 #[tokio::test]
625 async fn test_some_balance() {
626 let transaction = get_transaction(Some(U256::from(1)));
627 let validator = setup_validator(&transaction, 0);
628
629 let outcome = validator
630 .validate_transaction(TransactionOrigin::External, transaction.clone())
631 .await;
632
633 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
634 assert!(
635 err.to_string()
636 .contains("Native transfers are not supported")
637 );
638 } else {
639 panic!("Expected Invalid outcome with InsufficientFunds error");
640 }
641 }
642
643 #[tokio::test]
644 async fn test_aa_valid_before_check() {
645 let current_time = std::time::SystemTime::now()
647 .duration_since(std::time::UNIX_EPOCH)
648 .unwrap()
649 .as_secs();
650
651 let tx_no_valid_before = create_aa_transaction(None, None);
653 let validator = setup_validator(&tx_no_valid_before, current_time);
654 let outcome = validator
655 .validate_transaction(TransactionOrigin::External, tx_no_valid_before)
656 .await;
657
658 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
659 let error_msg = format!("{err}");
660 assert!(!error_msg.contains("valid_before"));
661 }
662
663 let tx_too_close =
665 create_aa_transaction(None, Some(current_time + AA_VALID_BEFORE_MIN_SECS));
666 let validator = setup_validator(&tx_too_close, current_time);
667 let outcome = validator
668 .validate_transaction(TransactionOrigin::External, tx_too_close)
669 .await;
670
671 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
672 let error_msg = format!("{err}");
673 assert!(
674 error_msg.contains("valid_before"),
675 "Expected 'valid_before' got: {error_msg}"
676 );
677 } else {
678 panic!("Expected invalid outcome with InvalidValidBefore error");
679 }
680
681 let tx_valid =
683 create_aa_transaction(None, Some(current_time + AA_VALID_BEFORE_MIN_SECS + 1));
684 let validator = setup_validator(&tx_valid, current_time);
685 let outcome = validator
686 .validate_transaction(TransactionOrigin::External, tx_valid)
687 .await;
688
689 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
690 let error_msg = format!("{err}");
691 assert!(!error_msg.contains("valid_before"));
692 }
693 }
694
695 #[tokio::test]
696 async fn test_aa_valid_after_check() {
697 let current_time = std::time::SystemTime::now()
699 .duration_since(std::time::UNIX_EPOCH)
700 .unwrap()
701 .as_secs();
702
703 let tx_no_valid_after = create_aa_transaction(None, None);
705 let validator = setup_validator(&tx_no_valid_after, current_time);
706 let outcome = validator
707 .validate_transaction(TransactionOrigin::External, tx_no_valid_after)
708 .await;
709
710 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
711 let error_msg = format!("{err}");
712 assert!(!error_msg.contains("valid_after"));
713 }
714
715 let tx_within_limit = create_aa_transaction(Some(current_time + 1800), None);
717 let validator = setup_validator(&tx_within_limit, current_time);
718 let outcome = validator
719 .validate_transaction(TransactionOrigin::External, tx_within_limit)
720 .await;
721
722 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
723 let error_msg = format!("{err}");
724 assert!(!error_msg.contains("valid_after"));
725 }
726
727 let tx_too_far = create_aa_transaction(Some(current_time + 7200), None);
729 let validator = setup_validator(&tx_too_far, current_time);
730 let outcome = validator
731 .validate_transaction(TransactionOrigin::External, tx_too_far)
732 .await;
733
734 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
735 let error_msg = format!("{err}");
736 assert!(error_msg.contains("valid_after"));
737 } else {
738 panic!("Expected invalid outcome with InvalidValidAfter error");
739 }
740 }
741
742 #[tokio::test]
743 async fn test_blacklisted_fee_payer_rejected() {
744 use alloy_primitives::{Signature, TxKind, address, uint};
745 use tempo_precompiles::{
746 TIP403_REGISTRY_ADDRESS,
747 tip20::slots as tip20_slots,
748 tip403_registry::{ITIP403Registry, PolicyData},
749 };
750 use tempo_primitives::transaction::{
751 TempoTransaction,
752 tempo_transaction::Call,
753 tt_signature::{PrimitiveSignature, TempoSignature},
754 tt_signed::AASigned,
755 };
756
757 let fee_token = address!("20C0000000000000000000000000000000000001");
759 let policy_id: u64 = 2;
760
761 let tx_aa = TempoTransaction {
763 chain_id: 1,
764 max_priority_fee_per_gas: 1_000_000_000,
765 max_fee_per_gas: 2_000_000_000,
766 gas_limit: 100_000,
767 calls: vec![Call {
768 to: TxKind::Call(address!("0000000000000000000000000000000000000001")),
769 value: U256::ZERO,
770 input: alloy_primitives::Bytes::new(),
771 }],
772 nonce_key: U256::ZERO,
773 nonce: 0,
774 fee_token: Some(fee_token),
775 fee_payer_signature: None,
776 valid_after: None,
777 valid_before: None,
778 access_list: Default::default(),
779 tempo_authorization_list: vec![],
780 key_authorization: None,
781 };
782
783 let signed_tx = AASigned::new_unhashed(
784 tx_aa,
785 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())),
786 );
787 let envelope: TempoTxEnvelope = signed_tx.into();
788 let recovered = envelope.try_into_recovered().unwrap();
789 let transaction = TempoPooledTransaction::new(recovered);
790 let fee_payer = transaction.sender();
791
792 let provider =
794 MockEthProvider::default().with_chain_spec(Arc::unwrap_or_clone(ANDANTINO.clone()));
795 provider.add_block(B256::random(), Block::default());
796
797 provider.add_account(
799 transaction.sender(),
800 ExtendedAccount::new(transaction.nonce(), U256::ZERO),
801 );
802
803 let usd_currency_value =
806 uint!(0x5553440000000000000000000000000000000000000000000000000000000006_U256);
807 provider.add_account(
808 fee_token,
809 ExtendedAccount::new(0, U256::ZERO).extend_storage([
810 (
811 tip20_slots::TRANSFER_POLICY_ID.into(),
812 U256::from(policy_id),
813 ),
814 (tip20_slots::CURRENCY.into(), usd_currency_value),
815 ]),
816 );
817
818 let policy_data = PolicyData {
820 policy_type: ITIP403Registry::PolicyType::BLACKLIST as u8,
821 admin: Address::ZERO,
822 };
823 let policy_data_slot = TIP403Registry::new().policy_data.at(policy_id).base_slot();
824 let policy_set_slot = TIP403Registry::new()
825 .policy_set
826 .at(policy_id)
827 .at(fee_payer)
828 .slot();
829
830 provider.add_account(
831 TIP403_REGISTRY_ADDRESS,
832 ExtendedAccount::new(0, U256::ZERO).extend_storage([
833 (policy_data_slot.into(), policy_data.encode_to_slot()),
834 (policy_set_slot.into(), U256::from(1)), ]),
836 );
837
838 let inner = EthTransactionValidatorBuilder::new(provider.clone())
840 .disable_balance_check()
841 .build(InMemoryBlobStore::default());
842 let validator =
843 TempoTransactionValidator::new(inner, 3600, AmmLiquidityCache::new(provider).unwrap());
844
845 let outcome = validator
846 .validate_transaction(TransactionOrigin::External, transaction)
847 .await;
848
849 match outcome {
851 TransactionValidationOutcome::Invalid(_, err) => {
852 let error_msg = err.to_string();
853 assert!(
854 error_msg.contains("blacklisted") || error_msg.contains("BlackListed"),
855 "Expected BlackListedFeePayer error, got: {error_msg}"
856 );
857 }
858 other => panic!("Expected Invalid outcome, got: {other:?}"),
859 }
860 }
861}