1#![allow(clippy::comparison_chain)]
19
20use alloc::vec::Vec;
21pub use evm::backend::Basic as Account;
22use frame_support::{sp_runtime::traits::UniqueSaturatedInto, weights::Weight};
23use sp_core::{H160, H256, U256};
24
25#[derive(Debug)]
26pub struct CheckEvmTransactionInput {
27 pub chain_id: Option<u64>,
28 pub to: Option<H160>,
29 pub input: Vec<u8>,
30 pub nonce: U256,
31 pub gas_limit: U256,
32 pub gas_price: Option<U256>,
33 pub max_fee_per_gas: Option<U256>,
34 pub max_priority_fee_per_gas: Option<U256>,
35 pub value: U256,
36 pub access_list: Vec<(H160, Vec<H256>)>,
37 pub authorization_list: Vec<(U256, H160, U256, Option<H160>)>,
38}
39
40#[derive(Debug)]
41pub struct CheckEvmTransactionConfig<'config> {
42 pub evm_config: &'config evm::Config,
43 pub block_gas_limit: U256,
44 pub base_fee: U256,
45 pub chain_id: u64,
46 pub is_transactional: bool,
47}
48
49#[derive(Debug)]
50pub struct CheckEvmTransaction<'config, E: From<TransactionValidationError>> {
51 pub config: CheckEvmTransactionConfig<'config>,
52 pub transaction: CheckEvmTransactionInput,
53 pub weight_limit: Option<Weight>,
54 pub proof_size_base_cost: Option<u64>,
55 _marker: core::marker::PhantomData<E>,
56}
57
58#[repr(u8)]
60#[derive(num_enum::FromPrimitive, num_enum::IntoPrimitive, Debug)]
61pub enum TransactionValidationError {
62 GasLimitTooLow,
64 GasLimitTooHigh,
66 GasPriceTooLow,
68 PriorityFeeTooHigh,
70 BalanceTooLow,
72 TxNonceTooLow,
74 TxNonceTooHigh,
76 InvalidFeeInput,
78 InvalidChainId,
80 InvalidSignature,
82 EmptyAuthorizationList,
88 AuthorizationListTooLarge,
101 #[num_enum(default)]
103 UnknownError,
104}
105
106impl<'config, E: From<TransactionValidationError>> CheckEvmTransaction<'config, E> {
107 pub fn new(
108 config: CheckEvmTransactionConfig<'config>,
109 transaction: CheckEvmTransactionInput,
110 weight_limit: Option<Weight>,
111 proof_size_base_cost: Option<u64>,
112 ) -> Self {
113 CheckEvmTransaction {
114 config,
115 transaction,
116 weight_limit,
117 proof_size_base_cost,
118 _marker: Default::default(),
119 }
120 }
121
122 pub fn validate_in_pool_for(&self, who: &Account) -> Result<&Self, E> {
123 if self.transaction.nonce < who.nonce {
124 return Err(TransactionValidationError::TxNonceTooLow.into());
125 }
126 self.validate_common()
127 }
128
129 pub fn validate_in_block_for(&self, who: &Account) -> Result<&Self, E> {
130 if self.transaction.nonce > who.nonce {
131 return Err(TransactionValidationError::TxNonceTooHigh.into());
132 } else if self.transaction.nonce < who.nonce {
133 return Err(TransactionValidationError::TxNonceTooLow.into());
134 }
135 self.validate_common()
136 }
137
138 pub fn with_chain_id(&self) -> Result<&Self, E> {
139 if let Some(chain_id) = self.transaction.chain_id {
141 if chain_id != self.config.chain_id {
142 return Err(TransactionValidationError::InvalidChainId.into());
143 }
144 }
145 Ok(self)
146 }
147
148 pub fn with_base_fee(&self) -> Result<&Self, E> {
149 let (gas_price, _) = self.transaction_fee_input()?;
151 if self.config.is_transactional || gas_price > U256::zero() {
152 if gas_price < self.config.base_fee {
154 return Err(TransactionValidationError::GasPriceTooLow.into());
155 }
156 }
157 Ok(self)
158 }
159
160 pub fn with_balance_for(&self, who: &Account) -> Result<&Self, E> {
161 let (max_fee_per_gas, _) = self.transaction_fee_input()?;
163
164 let fee = max_fee_per_gas.saturating_mul(self.transaction.gas_limit);
174 if self.config.is_transactional || fee > U256::zero() {
175 let total_payment = self.transaction.value.saturating_add(fee);
176 if who.balance < total_payment {
177 return Err(TransactionValidationError::BalanceTooLow.into());
178 }
179 }
180 Ok(self)
181 }
182
183 fn transaction_fee_input(&self) -> Result<(U256, Option<U256>), E> {
187 match (
188 self.transaction.gas_price,
189 self.transaction.max_fee_per_gas,
190 self.transaction.max_priority_fee_per_gas,
191 ) {
192 (Some(gas_price), None, None) => Ok((gas_price, Some(gas_price))),
194 (None, Some(max_fee_per_gas), None) => {
196 Ok((max_fee_per_gas, Some(self.config.base_fee)))
197 }
198 (None, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => {
200 if max_priority_fee_per_gas > max_fee_per_gas {
201 return Err(TransactionValidationError::PriorityFeeTooHigh.into());
202 }
203 let effective_gas_price = self
204 .config
205 .base_fee
206 .checked_add(max_priority_fee_per_gas)
207 .unwrap_or_else(U256::max_value)
208 .min(max_fee_per_gas);
209 Ok((max_fee_per_gas, Some(effective_gas_price)))
210 }
211 _ => {
212 if self.config.is_transactional {
213 Err(TransactionValidationError::InvalidFeeInput.into())
214 } else {
215 Ok((U256::zero(), None))
217 }
218 }
219 }
220 }
221
222 pub fn validate_common(&self) -> Result<&Self, E> {
223 if self.config.is_transactional {
224 if let (Some(weight_limit), Some(proof_size_base_cost)) =
227 (self.weight_limit, self.proof_size_base_cost)
228 {
229 let _ = weight_limit
230 .proof_size()
231 .checked_sub(proof_size_base_cost)
232 .ok_or(TransactionValidationError::GasLimitTooLow)?;
233 }
234
235 let mut gasometer = evm::gasometer::Gasometer::new(
238 self.transaction.gas_limit.unique_saturated_into(),
239 self.config.evm_config,
240 );
241 let transaction_cost = if self.transaction.to.is_some() {
242 evm::gasometer::call_transaction_cost(
243 &self.transaction.input,
244 &self.transaction.access_list,
245 &self.transaction.authorization_list,
246 )
247 } else {
248 evm::gasometer::create_transaction_cost(
249 &self.transaction.input,
250 &self.transaction.access_list,
251 &self.transaction.authorization_list,
252 )
253 };
254
255 if gasometer.record_transaction(transaction_cost).is_err() {
256 return Err(TransactionValidationError::GasLimitTooLow.into());
257 }
258
259 if self.transaction.gas_limit > self.config.block_gas_limit {
261 return Err(TransactionValidationError::GasLimitTooHigh.into());
262 }
263 }
264
265 Ok(self)
266 }
267
268 pub fn with_eip7702_authorization_list(&self, is_eip7702: bool) -> Result<&Self, E> {
286 if is_eip7702 {
287 if self.transaction.authorization_list.is_empty() {
290 return Err(TransactionValidationError::EmptyAuthorizationList.into());
291 }
292
293 const MAX_AUTHORIZATION_LIST_SIZE: usize = 255;
295 if self.transaction.authorization_list.len() > MAX_AUTHORIZATION_LIST_SIZE {
296 return Err(TransactionValidationError::AuthorizationListTooLarge.into());
297 }
298 }
299
300 Ok(self)
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[derive(Debug, PartialEq)]
309 pub enum TestError {
310 GasLimitTooLow,
311 GasLimitTooHigh,
312 GasPriceTooLow,
313 PriorityFeeTooHigh,
314 BalanceTooLow,
315 TxNonceTooLow,
316 TxNonceTooHigh,
317 InvalidFeeInput,
318 InvalidChainId,
319 InvalidSignature,
320 EmptyAuthorizationList,
321 AuthorizationListTooLarge,
322 UnknownError,
323 }
324
325 static PECTRA_CONFIG: evm::Config = evm::Config::pectra();
326
327 impl From<TransactionValidationError> for TestError {
328 fn from(e: TransactionValidationError) -> Self {
329 match e {
330 TransactionValidationError::GasLimitTooLow => TestError::GasLimitTooLow,
331 TransactionValidationError::GasLimitTooHigh => TestError::GasLimitTooHigh,
332 TransactionValidationError::GasPriceTooLow => TestError::GasPriceTooLow,
333 TransactionValidationError::PriorityFeeTooHigh => TestError::PriorityFeeTooHigh,
334 TransactionValidationError::BalanceTooLow => TestError::BalanceTooLow,
335 TransactionValidationError::TxNonceTooLow => TestError::TxNonceTooLow,
336 TransactionValidationError::TxNonceTooHigh => TestError::TxNonceTooHigh,
337 TransactionValidationError::InvalidFeeInput => TestError::InvalidFeeInput,
338 TransactionValidationError::InvalidChainId => TestError::InvalidChainId,
339 TransactionValidationError::InvalidSignature => TestError::InvalidSignature,
340 TransactionValidationError::EmptyAuthorizationList => {
341 TestError::EmptyAuthorizationList
342 }
343 TransactionValidationError::AuthorizationListTooLarge => {
344 TestError::AuthorizationListTooLarge
345 }
346 TransactionValidationError::UnknownError => TestError::UnknownError,
347 }
348 }
349 }
350
351 struct TestCase {
352 pub blockchain_gas_limit: U256,
353 pub blockchain_base_fee: U256,
354 pub blockchain_chain_id: u64,
355 pub is_transactional: bool,
356 pub chain_id: Option<u64>,
357 pub nonce: U256,
358 pub gas_limit: U256,
359 pub gas_price: Option<U256>,
360 pub max_fee_per_gas: Option<U256>,
361 pub max_priority_fee_per_gas: Option<U256>,
362 pub value: U256,
363 pub weight_limit: Option<Weight>,
364 pub proof_size_base_cost: Option<u64>,
365 }
366
367 impl Default for TestCase {
368 fn default() -> Self {
369 TestCase {
370 blockchain_gas_limit: U256::max_value(),
371 blockchain_base_fee: U256::from(1_000_000_000u128),
372 blockchain_chain_id: 42u64,
373 is_transactional: true,
374 chain_id: Some(42u64),
375 nonce: U256::zero(),
376 gas_limit: U256::from(21_000u64),
377 gas_price: None,
378 max_fee_per_gas: Some(U256::from(1_000_000_000u128)),
379 max_priority_fee_per_gas: Some(U256::from(1_000_000_000u128)),
380 value: U256::from(1u8),
381 weight_limit: None,
382 proof_size_base_cost: None,
383 }
384 }
385 }
386
387 fn test_env<'config>(input: TestCase) -> CheckEvmTransaction<'config, TestError> {
388 let TestCase {
389 blockchain_gas_limit,
390 blockchain_base_fee,
391 blockchain_chain_id,
392 is_transactional,
393 chain_id,
394 nonce,
395 gas_limit,
396 gas_price,
397 max_fee_per_gas,
398 max_priority_fee_per_gas,
399 value,
400 weight_limit,
401 proof_size_base_cost,
402 } = input;
403 CheckEvmTransaction::<TestError>::new(
404 CheckEvmTransactionConfig {
405 evm_config: &PECTRA_CONFIG,
406 block_gas_limit: blockchain_gas_limit,
407 base_fee: blockchain_base_fee,
408 chain_id: blockchain_chain_id,
409 is_transactional,
410 },
411 CheckEvmTransactionInput {
412 chain_id,
413 to: Some(H160::default()),
414 input: vec![],
415 nonce,
416 gas_limit,
417 gas_price,
418 max_fee_per_gas,
419 max_priority_fee_per_gas,
420 value,
421 access_list: vec![],
422 authorization_list: vec![],
423 },
424 weight_limit,
425 proof_size_base_cost,
426 )
427 }
428
429 fn default_transaction<'config>(
431 is_transactional: bool,
432 ) -> CheckEvmTransaction<'config, TestError> {
433 test_env(TestCase {
434 is_transactional,
435 ..Default::default()
436 })
437 }
438
439 fn transaction_gas_limit_low<'config>(
440 is_transactional: bool,
441 ) -> CheckEvmTransaction<'config, TestError> {
442 test_env(TestCase {
443 gas_limit: U256::from(1u8),
444 is_transactional,
445 ..Default::default()
446 })
447 }
448
449 fn transaction_gas_limit_low_proof_size<'config>(
450 is_transactional: bool,
451 ) -> CheckEvmTransaction<'config, TestError> {
452 test_env(TestCase {
453 weight_limit: Some(Weight::from_parts(1, 1)),
454 proof_size_base_cost: Some(2),
455 is_transactional,
456 ..Default::default()
457 })
458 }
459
460 fn transaction_gas_limit_high<'config>() -> CheckEvmTransaction<'config, TestError> {
461 test_env(TestCase {
462 blockchain_gas_limit: U256::from(1u8),
463 ..Default::default()
464 })
465 }
466
467 fn transaction_nonce_high<'config>() -> CheckEvmTransaction<'config, TestError> {
468 test_env(TestCase {
469 nonce: U256::from(10u8),
470 ..Default::default()
471 })
472 }
473
474 fn transaction_invalid_chain_id<'config>() -> CheckEvmTransaction<'config, TestError> {
475 test_env(TestCase {
476 chain_id: Some(555u64),
477 ..Default::default()
478 })
479 }
480
481 fn transaction_none_fee<'config>(
482 is_transactional: bool,
483 ) -> CheckEvmTransaction<'config, TestError> {
484 test_env(TestCase {
485 max_fee_per_gas: None,
486 max_priority_fee_per_gas: None,
487 is_transactional,
488 ..Default::default()
489 })
490 }
491
492 fn transaction_max_fee_low<'config>(
493 is_transactional: bool,
494 ) -> CheckEvmTransaction<'config, TestError> {
495 test_env(TestCase {
496 max_fee_per_gas: Some(U256::from(1u8)),
497 max_priority_fee_per_gas: None,
498 is_transactional,
499 ..Default::default()
500 })
501 }
502
503 fn transaction_priority_fee_high<'config>(
504 is_transactional: bool,
505 ) -> CheckEvmTransaction<'config, TestError> {
506 test_env(TestCase {
507 max_priority_fee_per_gas: Some(U256::from(1_100_000_000)),
508 is_transactional,
509 ..Default::default()
510 })
511 }
512
513 fn transaction_max_fee_high<'config>(tip: bool) -> CheckEvmTransaction<'config, TestError> {
514 let mut input = TestCase {
515 max_fee_per_gas: Some(U256::from(5_000_000_000u128)),
516 ..Default::default()
517 };
518 if !tip {
519 input.max_priority_fee_per_gas = None;
520 }
521 test_env(input)
522 }
523
524 fn legacy_transaction<'config>() -> CheckEvmTransaction<'config, TestError> {
525 test_env(TestCase {
526 gas_price: Some(U256::from(1_000_000_000u128)),
527 max_fee_per_gas: None,
528 max_priority_fee_per_gas: None,
529 ..Default::default()
530 })
531 }
532
533 fn invalid_transaction_mixed_fees<'config>(
534 is_transactional: bool,
535 ) -> CheckEvmTransaction<'config, TestError> {
536 test_env(TestCase {
537 gas_price: Some(U256::from(1_000_000_000u128)),
538 max_fee_per_gas: Some(U256::from(1_000_000_000u128)),
539 max_priority_fee_per_gas: None,
540 is_transactional,
541 ..Default::default()
542 })
543 }
544
545 #[test]
547 fn validate_in_pool_and_block_succeeds() {
548 let who = Account {
549 balance: U256::from(1_000_000u128),
550 nonce: U256::zero(),
551 };
552 let test = default_transaction(true);
553 assert!(test.validate_in_pool_for(&who).is_ok());
555 assert!(test.validate_in_block_for(&who).is_ok());
557 }
558
559 #[test]
561 fn validate_in_pool_and_block_fails_nonce_too_low() {
562 let who = Account {
563 balance: U256::from(1_000_000u128),
564 nonce: U256::from(1u8),
565 };
566 let test = default_transaction(true);
567 let res = test.validate_in_pool_for(&who);
569 assert!(res.is_err());
570 assert_eq!(res.unwrap_err(), TestError::TxNonceTooLow);
571 let res = test.validate_in_block_for(&who);
573 assert!(res.is_err());
574 assert_eq!(res.unwrap_err(), TestError::TxNonceTooLow);
575 }
576
577 #[test]
579 fn validate_in_pool_succeeds_nonce_too_high() {
580 let who = Account {
581 balance: U256::from(1_000_000u128),
582 nonce: U256::from(1u8),
583 };
584 let test = transaction_nonce_high();
585 let res = test.validate_in_pool_for(&who);
586 assert!(res.is_ok());
587 }
588
589 #[test]
591 fn validate_in_block_fails_nonce_too_high() {
592 let who = Account {
593 balance: U256::from(1_000_000u128),
594 nonce: U256::from(1u8),
595 };
596 let test = transaction_nonce_high();
597 let res = test.validate_in_block_for(&who);
598 assert!(res.is_err());
599 }
600
601 #[test]
603 fn validate_in_pool_and_block_transactional_fails_gas_limit_too_low() {
604 let who = Account {
605 balance: U256::from(1_000_000u128),
606 nonce: U256::zero(),
607 };
608 let is_transactional = true;
609 let test = transaction_gas_limit_low(is_transactional);
610 let res = test.validate_in_pool_for(&who);
612 assert!(res.is_err());
613 assert_eq!(res.unwrap_err(), TestError::GasLimitTooLow);
614 let res = test.validate_in_block_for(&who);
616 assert!(res.is_err());
617 assert_eq!(res.unwrap_err(), TestError::GasLimitTooLow);
618 }
619
620 #[test]
622 fn validate_in_pool_and_block_non_transactional_succeeds_gas_limit_too_low() {
623 let who = Account {
624 balance: U256::from(1_000_000u128),
625 nonce: U256::zero(),
626 };
627 let is_transactional = false;
628 let test = transaction_gas_limit_low(is_transactional);
629 let res = test.validate_in_pool_for(&who);
631 assert!(res.is_ok());
632 let res = test.validate_in_block_for(&who);
634 assert!(res.is_ok());
635 }
636
637 #[test]
639 fn validate_in_pool_and_block_transactional_fails_gas_limit_too_low_proof_size() {
640 let who = Account {
641 balance: U256::from(1_000_000u128),
642 nonce: U256::zero(),
643 };
644 let is_transactional = true;
645 let test = transaction_gas_limit_low_proof_size(is_transactional);
646 let res = test.validate_in_pool_for(&who);
648 assert!(res.is_err());
649 assert_eq!(res.unwrap_err(), TestError::GasLimitTooLow);
650 let res = test.validate_in_block_for(&who);
652 assert!(res.is_err());
653 assert_eq!(res.unwrap_err(), TestError::GasLimitTooLow);
654 }
655
656 #[test]
658 fn validate_in_pool_and_block_non_transactional_succeeds_gas_limit_too_low_proof_size() {
659 let who = Account {
660 balance: U256::from(1_000_000u128),
661 nonce: U256::zero(),
662 };
663 let is_transactional = false;
664 let test = transaction_gas_limit_low_proof_size(is_transactional);
665 let res = test.validate_in_pool_for(&who);
667 assert!(res.is_ok());
668 let res = test.validate_in_block_for(&who);
670 assert!(res.is_ok());
671 }
672
673 #[test]
675 fn validate_in_pool_for_fails_gas_limit_too_high() {
676 let who = Account {
677 balance: U256::from(1_000_000u128),
678 nonce: U256::zero(),
679 };
680 let test = transaction_gas_limit_high();
681 let res = test.validate_in_pool_for(&who);
683 assert!(res.is_err());
684 assert_eq!(res.unwrap_err(), TestError::GasLimitTooHigh);
685 let res = test.validate_in_block_for(&who);
687 assert!(res.is_err());
688 assert_eq!(res.unwrap_err(), TestError::GasLimitTooHigh);
689 }
690
691 #[test]
693 fn validate_chain_id_succeeds() {
694 let test = default_transaction(true);
695 let res = test.with_chain_id();
696 assert!(res.is_ok());
697 }
698
699 #[test]
701 fn validate_chain_id_fails() {
702 let test = transaction_invalid_chain_id();
703 let res = test.with_chain_id();
704 assert!(res.is_err());
705 assert_eq!(res.unwrap_err(), TestError::InvalidChainId);
706 }
707
708 #[test]
710 fn validate_base_fee_succeeds() {
711 let test = default_transaction(true);
713 let res = test.with_base_fee();
714 assert!(res.is_ok());
715 let test = default_transaction(false);
717 let res = test.with_base_fee();
718 assert!(res.is_ok());
719 }
720
721 #[test]
723 fn validate_base_fee_with_none_fee_fails() {
724 let test = transaction_none_fee(true);
725 let res = test.with_base_fee();
726 assert!(res.is_err());
727 assert_eq!(res.unwrap_err(), TestError::InvalidFeeInput);
728 }
729
730 #[test]
732 fn validate_base_fee_with_none_fee_non_transactional_succeeds() {
733 let test = transaction_none_fee(false);
734 let res = test.with_base_fee();
735 assert!(res.is_ok());
736 }
737
738 #[test]
740 fn validate_base_fee_with_max_fee_too_low_fails() {
741 let test = transaction_max_fee_low(true);
743 let res = test.with_base_fee();
744 assert!(res.is_err());
745 assert_eq!(res.unwrap_err(), TestError::GasPriceTooLow);
746 let test = transaction_max_fee_low(false);
748 let res = test.with_base_fee();
749 assert!(res.is_err());
750 assert_eq!(res.unwrap_err(), TestError::GasPriceTooLow);
751 }
752
753 #[test]
755 fn validate_base_fee_with_priority_fee_too_high_fails() {
756 let test = transaction_priority_fee_high(true);
758 let res = test.with_base_fee();
759 assert!(res.is_err());
760 assert_eq!(res.unwrap_err(), TestError::PriorityFeeTooHigh);
761 let test = transaction_priority_fee_high(false);
763 let res = test.with_base_fee();
764 assert!(res.is_err());
765 assert_eq!(res.unwrap_err(), TestError::PriorityFeeTooHigh);
766 }
767
768 #[test]
770 fn validate_balance_succeeds() {
771 let who = Account {
772 balance: U256::from(21_000_000_000_001u128),
773 nonce: U256::zero(),
774 };
775 let test = default_transaction(true);
777 let res = test.with_balance_for(&who);
778 assert!(res.is_ok());
779 let test = default_transaction(false);
781 let res = test.with_balance_for(&who);
782 assert!(res.is_ok());
783 }
784
785 #[test]
787 fn validate_insufficient_balance_fails() {
788 let who = Account {
789 balance: U256::from(21_000_000_000_000u128),
790 nonce: U256::zero(),
791 };
792 let test = default_transaction(true);
794 let res = test.with_balance_for(&who);
795 assert!(res.is_err());
796 assert_eq!(res.unwrap_err(), TestError::BalanceTooLow);
797 let test = default_transaction(false);
799 let res = test.with_balance_for(&who);
800 assert!(res.is_err());
801 assert_eq!(res.unwrap_err(), TestError::BalanceTooLow);
802 }
803
804 #[test]
806 fn validate_non_fee_transactional_fails() {
807 let who = Account {
808 balance: U256::from(21_000_000_000_001u128),
809 nonce: U256::zero(),
810 };
811 let test = transaction_none_fee(true);
812 let res = test.with_balance_for(&who);
813 assert!(res.is_err());
814 assert_eq!(res.unwrap_err(), TestError::InvalidFeeInput);
815 }
816
817 #[test]
819 fn validate_non_fee_non_transactional_succeeds() {
820 let who = Account {
821 balance: U256::from(0u8),
822 nonce: U256::zero(),
823 };
824 let test = transaction_none_fee(false);
825 let res = test.with_balance_for(&who);
826 assert!(res.is_ok());
827 }
828
829 #[test]
831 fn validate_balance_regardless_of_base_fee() {
832 let who = Account {
833 balance: U256::from(21_000_000_000_001u128),
835 nonce: U256::zero(),
836 };
837 let with_tip = false;
838 let test = transaction_max_fee_high(with_tip);
839 let res = test.with_balance_for(&who);
840 assert!(res.is_err());
841 }
842
843 #[test]
845 fn validate_balance_regardless_of_effective_gas_price() {
846 let who = Account {
847 balance: U256::from(42_000_000_000_001u128),
849 nonce: U256::zero(),
850 };
851 let with_tip = true;
852 let test = transaction_max_fee_high(with_tip);
853 let res = test.with_balance_for(&who);
854 assert!(res.is_err());
855 }
856
857 #[test]
859 fn validate_balance_for_legacy_transaction_succeeds() {
860 let who = Account {
861 balance: U256::from(21_000_000_000_001u128),
862 nonce: U256::zero(),
863 };
864 let test = legacy_transaction();
865 let res = test.with_balance_for(&who);
866 assert!(res.is_ok());
867 }
868
869 #[test]
871 fn validate_balance_for_legacy_transaction_fails() {
872 let who = Account {
873 balance: U256::from(21_000_000_000_000u128),
874 nonce: U256::zero(),
875 };
876 let test = legacy_transaction();
877 let res = test.with_balance_for(&who);
878 assert!(res.is_err());
879 assert_eq!(res.unwrap_err(), TestError::BalanceTooLow);
880 }
881
882 #[test]
884 fn validate_balance_with_invalid_fee_input() {
885 let who = Account {
886 balance: U256::from(21_000_000_000_001u128),
887 nonce: U256::zero(),
888 };
889 let is_transactional = true;
891 let test = invalid_transaction_mixed_fees(is_transactional);
892 let res = test.with_balance_for(&who);
893 assert!(res.is_err());
894 assert_eq!(res.unwrap_err(), TestError::InvalidFeeInput);
895 let is_transactional = false;
897 let test = invalid_transaction_mixed_fees(is_transactional);
898 let res = test.with_balance_for(&who);
899 assert!(res.is_ok());
900 }
901
902 #[test]
904 fn validate_base_fee_with_invalid_fee_input() {
905 let is_transactional = true;
907 let test = invalid_transaction_mixed_fees(is_transactional);
908 let res = test.with_base_fee();
909 assert!(res.is_err());
910 assert_eq!(res.unwrap_err(), TestError::InvalidFeeInput);
911 let is_transactional = false;
913 let test = invalid_transaction_mixed_fees(is_transactional);
914 let res = test.with_base_fee();
915 assert!(res.is_ok());
916 }
917
918 #[test]
920 fn validate_eip7702_empty_authorization_list_fails() {
921 let validator = CheckEvmTransaction::<TestError>::new(
922 CheckEvmTransactionConfig {
923 evm_config: &PECTRA_CONFIG,
924 block_gas_limit: U256::from(1_000_000u64),
925 base_fee: U256::from(1_000_000_000u128),
926 chain_id: 42u64,
927 is_transactional: true,
928 },
929 CheckEvmTransactionInput {
930 chain_id: Some(42u64),
931 to: Some(H160::default()),
932 input: vec![],
933 nonce: U256::zero(),
934 gas_limit: U256::from(21_000u64),
935 gas_price: None,
936 max_fee_per_gas: Some(U256::from(1_000_000_000u128)),
937 max_priority_fee_per_gas: Some(U256::from(1_000_000_000u128)),
938 value: U256::zero(),
939 access_list: vec![],
940 authorization_list: vec![], },
942 None,
943 None,
944 );
945
946 let res = validator.with_eip7702_authorization_list(true);
947 assert!(res.is_err());
948 assert_eq!(res.unwrap_err(), TestError::EmptyAuthorizationList);
949 }
950
951 #[test]
952 fn validate_eip7702_authorization_list_too_large_fails() {
953 let authorization_list: Vec<(U256, H160, U256, Option<H160>)> = (0..256)
955 .map(|i| (U256::from(42u64), H160::default(), U256::from(i), None))
956 .collect();
957
958 let validator = CheckEvmTransaction::<TestError>::new(
959 CheckEvmTransactionConfig {
960 evm_config: &PECTRA_CONFIG,
961 block_gas_limit: U256::from(1_000_000u64),
962 base_fee: U256::from(1_000_000_000u128),
963 chain_id: 42u64,
964 is_transactional: true,
965 },
966 CheckEvmTransactionInput {
967 chain_id: Some(42u64),
968 to: Some(H160::default()),
969 input: vec![],
970 nonce: U256::zero(),
971 gas_limit: U256::from(21_000u64),
972 gas_price: None,
973 max_fee_per_gas: Some(U256::from(1_000_000_000u128)),
974 max_priority_fee_per_gas: Some(U256::from(1_000_000_000u128)),
975 value: U256::zero(),
976 access_list: vec![],
977 authorization_list,
978 },
979 None,
980 None,
981 );
982
983 let res = validator.with_eip7702_authorization_list(true);
984 assert!(res.is_err());
985 assert_eq!(res.unwrap_err(), TestError::AuthorizationListTooLarge);
986 }
987
988 #[test]
989 fn validate_eip7702_valid_authorization_list_succeeds() {
990 let authorization_list = vec![
991 (U256::from(42u64), H160::default(), U256::zero(), None), (U256::zero(), H160::default(), U256::from(1), None), ];
994
995 let validator = CheckEvmTransaction::<TestError>::new(
996 CheckEvmTransactionConfig {
997 evm_config: &PECTRA_CONFIG,
998 block_gas_limit: U256::from(1_000_000u64),
999 base_fee: U256::from(1_000_000_000u128),
1000 chain_id: 42u64,
1001 is_transactional: true,
1002 },
1003 CheckEvmTransactionInput {
1004 chain_id: Some(42u64),
1005 to: Some(H160::default()),
1006 input: vec![],
1007 nonce: U256::zero(),
1008 gas_limit: U256::from(21_000u64),
1009 gas_price: None,
1010 max_fee_per_gas: Some(U256::from(1_000_000_000u128)),
1011 max_priority_fee_per_gas: Some(U256::from(1_000_000_000u128)),
1012 value: U256::zero(),
1013 access_list: vec![],
1014 authorization_list,
1015 },
1016 None,
1017 None,
1018 );
1019
1020 let res = validator.with_eip7702_authorization_list(true);
1021 assert!(res.is_ok());
1022 }
1023
1024 #[test]
1025 fn validate_non_eip7702_transaction_skips_authorization_validation() {
1026 let validator = CheckEvmTransaction::<TestError>::new(
1028 CheckEvmTransactionConfig {
1029 evm_config: &PECTRA_CONFIG,
1030 block_gas_limit: U256::from(1_000_000u64),
1031 base_fee: U256::from(1_000_000_000u128),
1032 chain_id: 42u64,
1033 is_transactional: true,
1034 },
1035 CheckEvmTransactionInput {
1036 chain_id: Some(42u64),
1037 to: Some(H160::default()),
1038 input: vec![],
1039 nonce: U256::zero(),
1040 gas_limit: U256::from(21_000u64),
1041 gas_price: None,
1042 max_fee_per_gas: Some(U256::from(1_000_000_000u128)),
1043 max_priority_fee_per_gas: Some(U256::from(1_000_000_000u128)),
1044 value: U256::zero(),
1045 access_list: vec![],
1046 authorization_list: vec![], },
1048 None,
1049 None,
1050 );
1051
1052 let res = validator.with_eip7702_authorization_list(false); assert!(res.is_ok());
1054 }
1055}