fp_evm/
validation.rs

1// This file is part of Frontier.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![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/// Transaction validation errors
59#[repr(u8)]
60#[derive(num_enum::FromPrimitive, num_enum::IntoPrimitive, Debug)]
61pub enum TransactionValidationError {
62	/// The transaction gas limit is too low
63	GasLimitTooLow,
64	/// The transaction gas limit is too hign
65	GasLimitTooHigh,
66	/// The transaction gas price is too low
67	GasPriceTooLow,
68	/// The transaction priority fee is too high
69	PriorityFeeTooHigh,
70	/// The transaction balance is too low
71	BalanceTooLow,
72	/// The transaction nonce is too low
73	TxNonceTooLow,
74	/// The transaction nonce is too high
75	TxNonceTooHigh,
76	/// The transaction fee input is invalid
77	InvalidFeeInput,
78	/// The chain id is incorrect
79	InvalidChainId,
80	/// The transaction signature is invalid
81	InvalidSignature,
82	/// EIP-7702 transaction has empty authorization list
83	///
84	/// According to EIP-7702 specification, transactions with empty authorization lists are invalid.
85	/// This validates the fundamental requirement that EIP-7702 transactions must include at least
86	/// one authorization to be valid.
87	EmptyAuthorizationList,
88	/// EIP-7702 authorization list exceeds maximum size
89	///
90	/// To prevent DoS attacks, authorization lists are limited to a maximum of 255 items.
91	/// This provides reasonable authorization functionality while preventing excessive
92	/// resource consumption during validation and processing.
93	///
94	/// Rationale
95	/// - **Geth**: No explicit limit, relies on 32KB transaction size limit (~160 authorizations practical maximum)
96	/// - **EIP-7702 Spec**: No defined limit, left to implementations
97	///
98	/// This explicit limit is more predictable than implicit limits based on transaction size,
99	/// providing developers with clear boundaries and better DoS protection.
100	AuthorizationListTooLarge,
101	/// Unknown error
102	#[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		// Chain id matches the one in the signature.
140		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		// Get fee data from either a legacy or typed transaction input.
150		let (gas_price, _) = self.transaction_fee_input()?;
151		if self.config.is_transactional || gas_price > U256::zero() {
152			// Transaction max fee is at least the current base fee.
153			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		// Get fee data from either a legacy or typed transaction input.
162		let (max_fee_per_gas, _) = self.transaction_fee_input()?;
163
164		// Account has enough funds to pay for the transaction.
165		// Check is skipped on non-transactional calls that don't provide
166		// a gas price input.
167		//
168		// Validation for EIP-1559 is done using the max_fee_per_gas, which is
169		// the most a txn could possibly pay.
170		//
171		// Fee for Legacy or EIP-2930 transaction is calculated using
172		// the provided `gas_price`.
173		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	// Returns the max_fee_per_gas (or gas_price for legacy txns) as well as an optional
184	// effective_gas_price for EIP-1559 transactions. effective_gas_price represents
185	// the total (fee + tip) that would be paid given the current base_fee.
186	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			// Legacy or EIP-2930 transaction.
193			(Some(gas_price), None, None) => Ok((gas_price, Some(gas_price))),
194			// EIP-1559 transaction without tip.
195			(None, Some(max_fee_per_gas), None) => {
196				Ok((max_fee_per_gas, Some(self.config.base_fee)))
197			}
198			// EIP-1559 tip.
199			(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					// Allow non-set fee input for non-transactional calls.
216					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			// Try to subtract the proof_size_base_cost from the Weight proof_size limit or fail.
225			// Validate the weight limit can afford recording the proof size cost.
226			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			// We must ensure a transaction can pay the cost of its data bytes.
236			// If it can't it should not be included in a block.
237			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			// Transaction gas limit is within the upper bound block gas limit.
260			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	/// Validates EIP-7702 authorization list requirements at the Substrate level.
269	///
270	/// This function performs Substrate-specific validation for EIP-7702 authorization lists,
271	/// which complements the EVM-level validation performed by the EVM crate.
272	///
273	/// # EIP-7702 Validation Rules
274	///
275	/// ## Authorization List Requirements (when `is_eip7702` is true):
276	/// 1. **Non-empty**: Authorization list cannot be empty (per EIP-7702 spec)
277	/// 2. **Size limit**: Maximum 255 authorizations (DoS protection)
278	///
279	/// ## EVM-level Validation (handled by `evm`/`ethereum` crates):
280	/// - Authorization signature verification
281	/// - Nonce validation against authority accounts
282	/// - Delegation designator creation and management
283	/// - Gas cost calculation for authorizations
284	///
285	pub fn with_eip7702_authorization_list(&self, is_eip7702: bool) -> Result<&Self, E> {
286		if is_eip7702 {
287			// EIP-7702 validation: Check if authorization list is empty
288			// According to EIP-7702 specification: "The transaction is also considered invalid when the length of authorization_list is zero."
289			if self.transaction.authorization_list.is_empty() {
290				return Err(TransactionValidationError::EmptyAuthorizationList.into());
291			}
292
293			// EIP-7702 validation: Check authorization list size (DoS protection)
294			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	// Transaction settings
430	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	// Default (valid) transaction succeeds in pool and in block.
546	#[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		// Pool
554		assert!(test.validate_in_pool_for(&who).is_ok());
555		// Block
556		assert!(test.validate_in_block_for(&who).is_ok());
557	}
558
559	// Nonce too low fails in pool and in block.
560	#[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		// Pool
568		let res = test.validate_in_pool_for(&who);
569		assert!(res.is_err());
570		assert_eq!(res.unwrap_err(), TestError::TxNonceTooLow);
571		// Block
572		let res = test.validate_in_block_for(&who);
573		assert!(res.is_err());
574		assert_eq!(res.unwrap_err(), TestError::TxNonceTooLow);
575	}
576
577	// Nonce too high succeeds in pool.
578	#[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	// Nonce too high fails in block.
590	#[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	// Gas limit too low transactional fails in pool and in block.
602	#[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		// Pool
611		let res = test.validate_in_pool_for(&who);
612		assert!(res.is_err());
613		assert_eq!(res.unwrap_err(), TestError::GasLimitTooLow);
614		// Block
615		let res = test.validate_in_block_for(&who);
616		assert!(res.is_err());
617		assert_eq!(res.unwrap_err(), TestError::GasLimitTooLow);
618	}
619
620	// Gas limit too low non-transactional succeeds in pool and in block.
621	#[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		// Pool
630		let res = test.validate_in_pool_for(&who);
631		assert!(res.is_ok());
632		// Block
633		let res = test.validate_in_block_for(&who);
634		assert!(res.is_ok());
635	}
636
637	// Gas limit too low for proof size recording transactional fails in pool and in block.
638	#[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		// Pool
647		let res = test.validate_in_pool_for(&who);
648		assert!(res.is_err());
649		assert_eq!(res.unwrap_err(), TestError::GasLimitTooLow);
650		// Block
651		let res = test.validate_in_block_for(&who);
652		assert!(res.is_err());
653		assert_eq!(res.unwrap_err(), TestError::GasLimitTooLow);
654	}
655
656	// Gas limit too low non-transactional succeeds in pool and in block.
657	#[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		// Pool
666		let res = test.validate_in_pool_for(&who);
667		assert!(res.is_ok());
668		// Block
669		let res = test.validate_in_block_for(&who);
670		assert!(res.is_ok());
671	}
672
673	// Gas limit too high fails in pool and in block.
674	#[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		// Pool
682		let res = test.validate_in_pool_for(&who);
683		assert!(res.is_err());
684		assert_eq!(res.unwrap_err(), TestError::GasLimitTooHigh);
685		// Block
686		let res = test.validate_in_block_for(&who);
687		assert!(res.is_err());
688		assert_eq!(res.unwrap_err(), TestError::GasLimitTooHigh);
689	}
690
691	// Valid chain id succeeds.
692	#[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	// Invalid chain id fails.
700	#[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	// Valid max fee per gas succeeds.
709	#[test]
710	fn validate_base_fee_succeeds() {
711		// Transactional
712		let test = default_transaction(true);
713		let res = test.with_base_fee();
714		assert!(res.is_ok());
715		// Non-transactional
716		let test = default_transaction(false);
717		let res = test.with_base_fee();
718		assert!(res.is_ok());
719	}
720
721	// Transactional call with unset fee data fails.
722	#[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	// Non-transactional call with unset fee data succeeds.
731	#[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	// Max fee per gas too low fails.
739	#[test]
740	fn validate_base_fee_with_max_fee_too_low_fails() {
741		// Transactional
742		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		// Non-transactional
747		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	// Priority fee too high fails.
754	#[test]
755	fn validate_base_fee_with_priority_fee_too_high_fails() {
756		// Transactional
757		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		// Non-transactional
762		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	// Sufficient balance succeeds.
769	#[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		// Transactional
776		let test = default_transaction(true);
777		let res = test.with_balance_for(&who);
778		assert!(res.is_ok());
779		// Non-transactional
780		let test = default_transaction(false);
781		let res = test.with_balance_for(&who);
782		assert!(res.is_ok());
783	}
784
785	// Insufficient balance fails.
786	#[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		// Transactional
793		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		// Non-transactional
798		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	// Fee not set on transactional fails.
805	#[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	// Fee not set on non-transactional succeeds.
818	#[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	// Account balance is matched against max_fee_per_gas (without txn tip)
830	#[test]
831	fn validate_balance_regardless_of_base_fee() {
832		let who = Account {
833			// sufficient for base_fee, but not for max_fee_per_gas
834			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	// Account balance is matched against max_fee_per_gas (with txn tip)
844	#[test]
845	fn validate_balance_regardless_of_effective_gas_price() {
846		let who = Account {
847			// sufficient for (base_fee + tip), but not for max_fee_per_gas
848			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	// Account balance is matched against the provided gas_price for Legacy transactions.
858	#[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	// Account balance is matched against the provided gas_price for Legacy transactions.
870	#[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	// Transaction with invalid fee input - mixing gas_price and max_fee_per_gas.
883	#[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		// Fails for transactional.
890		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		// Succeeds for non-transactional.
896		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	// Transaction with invalid fee input - mixing gas_price and max_fee_per_gas.
903	#[test]
904	fn validate_base_fee_with_invalid_fee_input() {
905		// Fails for transactional.
906		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		// Succeeds for non-transactional.
912		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	// EIP-7702 Authorization list validation tests
919	#[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![], // Empty authorization list
941			},
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		// Create authorization list with 256 items (exceeds MAX_AUTHORIZATION_LIST_SIZE = 255)
954		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), // Matching chain ID
992			(U256::zero(), H160::default(), U256::from(1), None),     // Cross-chain (0)
993		];
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		// Empty authorization list should be OK for non-EIP-7702 transactions
1027		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![], // Empty authorization list
1047			},
1048			None,
1049			None,
1050		);
1051
1052		let res = validator.with_eip7702_authorization_list(false); // Not EIP-7702
1053		assert!(res.is_ok());
1054	}
1055}