1use context_interface::{
2 cfg::GasParams,
3 result::{InvalidHeader, InvalidTransaction},
4 transaction::{Transaction, TransactionType},
5 Block, Cfg, ContextTr,
6};
7use core::cmp;
8use interpreter::InitialAndFloorGas;
9use primitives::{eip4844, hardfork::SpecId, B256};
10
11pub fn validate_env<CTX: ContextTr, ERROR: From<InvalidHeader> + From<InvalidTransaction>>(
13 context: CTX,
14) -> Result<(), ERROR> {
15 let spec = context.cfg().spec().into();
16 if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
18 return Err(InvalidHeader::PrevrandaoNotSet.into());
19 }
20 if spec.is_enabled_in(SpecId::CANCUN) && context.block().blob_excess_gas_and_price().is_none() {
22 return Err(InvalidHeader::ExcessBlobGasNotSet.into());
23 }
24 validate_tx_env::<CTX>(context, spec).map_err(Into::into)
25}
26
27#[inline]
29pub const fn validate_legacy_gas_price(
30 gas_price: u128,
31 base_fee: Option<u128>,
32) -> Result<(), InvalidTransaction> {
33 if let Some(base_fee) = base_fee {
35 if gas_price < base_fee {
36 return Err(InvalidTransaction::GasPriceLessThanBasefee);
37 }
38 }
39 Ok(())
40}
41
42pub fn validate_priority_fee_tx(
44 max_fee: u128,
45 max_priority_fee: u128,
46 base_fee: Option<u128>,
47 disable_priority_fee_check: bool,
48) -> Result<(), InvalidTransaction> {
49 if !disable_priority_fee_check && max_priority_fee > max_fee {
50 return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
52 }
53
54 if let Some(base_fee) = base_fee {
56 let effective_gas_price = cmp::min(max_fee, base_fee.saturating_add(max_priority_fee));
57 if effective_gas_price < base_fee {
58 return Err(InvalidTransaction::GasPriceLessThanBasefee);
59 }
60 }
61
62 Ok(())
63}
64
65#[inline]
67fn validate_priority_fee_for_tx<TX: Transaction>(
68 tx: TX,
69 base_fee: Option<u128>,
70 disable_priority_fee_check: bool,
71) -> Result<(), InvalidTransaction> {
72 validate_priority_fee_tx(
73 tx.max_fee_per_gas(),
74 tx.max_priority_fee_per_gas().unwrap_or_default(),
75 base_fee,
76 disable_priority_fee_check,
77 )
78}
79
80pub fn validate_eip4844_tx(
82 blobs: &[B256],
83 max_blob_fee: u128,
84 block_blob_gas_price: u128,
85 max_blobs: Option<u64>,
86) -> Result<(), InvalidTransaction> {
87 if block_blob_gas_price > max_blob_fee {
89 return Err(InvalidTransaction::BlobGasPriceGreaterThanMax {
90 block_blob_gas_price,
91 tx_max_fee_per_blob_gas: max_blob_fee,
92 });
93 }
94
95 if blobs.is_empty() {
97 return Err(InvalidTransaction::EmptyBlobs);
98 }
99
100 for blob in blobs {
102 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
103 return Err(InvalidTransaction::BlobVersionNotSupported);
104 }
105 }
106
107 if let Some(max_blobs) = max_blobs {
110 if blobs.len() > max_blobs as usize {
111 return Err(InvalidTransaction::TooManyBlobs {
112 have: blobs.len(),
113 max: max_blobs as usize,
114 });
115 }
116 }
117 Ok(())
118}
119
120pub fn validate_tx_env<CTX: ContextTr>(
122 context: CTX,
123 spec_id: SpecId,
124) -> Result<(), InvalidTransaction> {
125 let tx = context.tx();
127 let tx_type = tx.tx_type();
128
129 let base_fee = if context.cfg().is_base_fee_check_disabled() {
130 None
131 } else {
132 Some(context.block().basefee() as u128)
133 };
134
135 let tx_type = TransactionType::from(tx_type);
136
137 if context.cfg().tx_chain_id_check() {
140 if let Some(chain_id) = tx.chain_id() {
141 if chain_id != context.cfg().chain_id() {
142 return Err(InvalidTransaction::InvalidChainId);
143 }
144 } else if !tx_type.is_legacy() && !tx_type.is_custom() {
145 return Err(InvalidTransaction::MissingChainId);
147 }
148 }
149
150 if !context.cfg().is_amsterdam_eip8037_enabled() {
152 let cap = context.cfg().tx_gas_limit_cap();
154 if tx.gas_limit() > cap {
155 return Err(InvalidTransaction::TxGasLimitGreaterThanCap {
156 gas_limit: tx.gas_limit(),
157 cap,
158 });
159 }
160 }
161
162 let disable_priority_fee_check = context.cfg().is_priority_fee_check_disabled();
163
164 match tx_type {
165 TransactionType::Legacy => {
166 validate_legacy_gas_price(tx.gas_price(), base_fee)?;
167 }
168 TransactionType::Eip2930 => {
169 if !spec_id.is_enabled_in(SpecId::BERLIN) {
171 return Err(InvalidTransaction::Eip2930NotSupported);
172 }
173 validate_legacy_gas_price(tx.gas_price(), base_fee)?;
174 }
175 TransactionType::Eip1559 => {
176 if !spec_id.is_enabled_in(SpecId::LONDON) {
177 return Err(InvalidTransaction::Eip1559NotSupported);
178 }
179 validate_priority_fee_for_tx(tx, base_fee, disable_priority_fee_check)?;
180 }
181 TransactionType::Eip4844 => {
182 if !spec_id.is_enabled_in(SpecId::CANCUN) {
183 return Err(InvalidTransaction::Eip4844NotSupported);
184 }
185
186 validate_priority_fee_for_tx(tx, base_fee, disable_priority_fee_check)?;
187
188 validate_eip4844_tx(
189 tx.blob_versioned_hashes(),
190 tx.max_fee_per_blob_gas(),
191 context.block().blob_gasprice().unwrap_or_default(),
192 context.cfg().max_blobs_per_tx(),
193 )?;
194 }
195 TransactionType::Eip7702 => {
196 if !spec_id.is_enabled_in(SpecId::PRAGUE) {
198 return Err(InvalidTransaction::Eip7702NotSupported);
199 }
200
201 validate_priority_fee_for_tx(tx, base_fee, disable_priority_fee_check)?;
202
203 let auth_list_len = tx.authorization_list_len();
204 if auth_list_len == 0 {
206 return Err(InvalidTransaction::EmptyAuthorizationList);
207 }
208 }
209 TransactionType::Custom => {
210 }
212 };
213
214 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
218 {
219 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
220 }
221
222 if spec_id.is_enabled_in(SpecId::SHANGHAI)
224 && tx.kind().is_create()
225 && tx.input().len() > context.cfg().max_initcode_size()
226 {
227 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
228 }
229
230 if tx.nonce() == u64::MAX {
233 return Err(InvalidTransaction::NonceOverflowInTransaction);
234 }
235
236 Ok(())
237}
238
239#[allow(clippy::too_many_arguments)]
244pub fn validate_initial_tx_gas(
245 tx: impl Transaction,
246 spec: SpecId,
247 is_eip7623_disabled: bool,
248 is_amsterdam_eip8037_enabled: bool,
249 tx_gas_limit_cap: u64,
250 eip2780: Option<context_interface::cfg::gas_params::Eip2780TxInfo>,
251) -> Result<InitialAndFloorGas, InvalidTransaction> {
252 validate_initial_tx_gas_with_gas_params(
253 tx,
254 spec,
255 &GasParams::new_spec(spec),
256 is_eip7623_disabled,
257 is_amsterdam_eip8037_enabled,
258 tx_gas_limit_cap,
259 eip2780,
260 )
261}
262
263#[allow(clippy::too_many_arguments)]
265pub fn validate_initial_tx_gas_with_gas_params(
266 tx: impl Transaction,
267 spec: SpecId,
268 gas_params: &GasParams,
269 is_eip7623_disabled: bool,
270 is_amsterdam_eip8037_enabled: bool,
271 tx_gas_limit_cap: u64,
272 eip2780: Option<context_interface::cfg::gas_params::Eip2780TxInfo>,
273) -> Result<InitialAndFloorGas, InvalidTransaction> {
274 let mut gas = gas_params.initial_tx_gas_for_tx(&tx, eip2780);
275
276 if is_eip7623_disabled {
277 gas.set_floor_gas(0);
278 }
279
280 if !is_amsterdam_eip8037_enabled {
281 gas.set_initial_state_gas(0);
282 }
283
284 if gas.initial_total_gas() > tx.gas_limit() {
286 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
287 gas_limit: tx.gas_limit(),
288 initial_gas: gas.initial_total_gas(),
289 });
290 }
291
292 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas() > tx.gas_limit() {
295 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
296 gas_floor: gas.floor_gas(),
297 gas_limit: tx.gas_limit(),
298 });
299 };
300
301 if is_amsterdam_eip8037_enabled && tx.gas_limit() > tx_gas_limit_cap {
305 let min_regular_gas = gas.initial_regular_gas().max(gas.floor_gas());
306 if min_regular_gas > tx_gas_limit_cap {
307 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
308 gas_floor: min_regular_gas,
309 gas_limit: tx_gas_limit_cap,
310 });
311 }
312 }
313
314 Ok(gas)
315}
316
317#[cfg(test)]
318mod tests {
319 use crate::{api::ExecuteEvm, ExecuteCommitEvm, MainBuilder, MainContext};
320 use bytecode::opcode;
321 use context::{
322 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
323 Context, ContextTr, TxEnv,
324 };
325 use database::{CacheDB, EmptyDB};
326 use primitives::{address, eip3860, eip7954, hardfork::SpecId, Bytes, TxKind, B256};
327 use state::{AccountInfo, Bytecode};
328
329 fn deploy_contract(
330 bytecode: Bytes,
331 spec_id: Option<SpecId>,
332 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
333 let ctx = Context::mainnet()
334 .modify_cfg_chained(|c| {
335 if let Some(spec_id) = spec_id {
336 c.set_spec_and_mainnet_gas_params(spec_id);
337 }
338 })
339 .modify_block_chained(|block| block.gas_limit = 100_000_000)
340 .with_db(CacheDB::<EmptyDB>::default());
341
342 let mut evm = ctx.build_mainnet();
343 evm.transact_commit(
344 TxEnv::builder()
345 .kind(TxKind::Create)
346 .data(bytecode.clone())
347 .build()
348 .unwrap(),
349 )
350 }
351
352 #[test]
353 fn test_eip3860_initcode_size_limit_failure() {
354 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
355 let bytecode: Bytes = large_bytecode.into();
356 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
357 assert!(matches!(
358 result,
359 Err(EVMError::Transaction(
360 InvalidTransaction::CreateInitCodeSizeLimit
361 ))
362 ));
363 }
364
365 #[test]
366 fn test_eip3860_initcode_size_limit_success_prague() {
367 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
368 let bytecode: Bytes = large_bytecode.into();
369 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
370 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
371 }
372
373 #[test]
374 fn test_eip7954_initcode_size_limit_failure_amsterdam() {
375 let large_bytecode = vec![opcode::STOP; eip7954::MAX_INITCODE_SIZE + 1];
376 let bytecode: Bytes = large_bytecode.into();
377 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
378 assert!(matches!(
379 result,
380 Err(EVMError::Transaction(
381 InvalidTransaction::CreateInitCodeSizeLimit
382 ))
383 ));
384 }
385
386 #[test]
387 fn test_eip7954_initcode_size_limit_success_amsterdam() {
388 let large_bytecode = vec![opcode::STOP; eip7954::MAX_INITCODE_SIZE];
389 let bytecode: Bytes = large_bytecode.into();
390 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
391 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
392 }
393
394 #[test]
395 fn test_eip7954_initcode_between_old_and_new_limit() {
396 let size = eip3860::MAX_INITCODE_SIZE + 1; let large_bytecode = vec![opcode::STOP; size];
400
401 let bytecode: Bytes = large_bytecode.clone().into();
403 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
404 assert!(matches!(
405 result,
406 Err(EVMError::Transaction(
407 InvalidTransaction::CreateInitCodeSizeLimit
408 ))
409 ));
410
411 let bytecode: Bytes = large_bytecode.into();
413 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
414 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
415 }
416
417 #[test]
418 fn test_eip7954_code_size_limit_failure() {
419 let init_code = vec![
425 0x62, 0x01, 0x00, 0x01, 0x60, 0x00, 0xf3, ];
429 let bytecode: Bytes = init_code.into();
430 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
431 assert!(
432 matches!(
433 result,
434 Ok(ExecutionResult::Halt {
435 reason: HaltReason::CreateContractSizeLimit,
436 ..
437 },)
438 ),
439 "{result:?}"
440 );
441 }
442
443 #[test]
444 fn test_eip170_code_size_limit_failure() {
445 let init_code = vec![
450 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
454 let bytecode: Bytes = init_code.into();
455 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
456 assert!(
457 matches!(
458 result,
459 Ok(ExecutionResult::Halt {
460 reason: HaltReason::CreateContractSizeLimit,
461 ..
462 },)
463 ),
464 "{result:?}"
465 );
466 }
467
468 #[test]
469 fn test_eip170_code_size_limit_success() {
470 let init_code = vec![
475 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
479 let bytecode: Bytes = init_code.into();
480 let result = deploy_contract(bytecode, None);
481 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
482 }
483
484 #[test]
485 fn test_eip170_create_opcode_size_limit_failure() {
486 let factory_code = vec![
506 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
523
524 let factory_bytecode: Bytes = factory_code.into();
526 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
527 .expect("factory contract deployment failed");
528
529 let factory_address = match &factory_result {
531 ExecutionResult::Success {
532 output: Output::Create(_, Some(addr)),
533 ..
534 } => *addr,
535 _ => panic!("factory contract deployment failed: {factory_result:?}"),
536 };
537
538 let tx_caller = address!("0x0000000000000000000000000000000000100000");
540 let call_result = Context::mainnet()
541 .with_db(CacheDB::<EmptyDB>::default())
542 .build_mainnet()
543 .transact_commit(
544 TxEnv::builder()
545 .caller(tx_caller)
546 .kind(TxKind::Call(factory_address))
547 .data(Bytes::new())
548 .build()
549 .unwrap(),
550 )
551 .expect("call factory contract failed");
552
553 match &call_result {
554 ExecutionResult::Success { output, .. } => match output {
555 Output::Call(bytes) => {
556 if !bytes.is_empty() {
557 assert!(
558 bytes.iter().all(|&b| b == 0),
559 "When CREATE operation failed, it should return all zero address"
560 );
561 }
562 }
563 _ => panic!("unexpected output type"),
564 },
565 _ => panic!("execution result is not Success"),
566 }
567 }
568
569 #[test]
570 fn test_eip170_create_opcode_size_limit_success() {
571 let factory_code = vec![
591 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
608
609 let factory_bytecode: Bytes = factory_code.into();
611 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
612 .expect("factory contract deployment failed");
613 let factory_address = match &factory_result {
615 ExecutionResult::Success {
616 output: Output::Create(_, Some(addr)),
617 ..
618 } => *addr,
619 _ => panic!("factory contract deployment failed: {factory_result:?}"),
620 };
621
622 let tx_caller = address!("0x0000000000000000000000000000000000100000");
624 let call_result = Context::mainnet()
625 .with_db(CacheDB::<EmptyDB>::default())
626 .build_mainnet()
627 .transact_commit(
628 TxEnv::builder()
629 .caller(tx_caller)
630 .kind(TxKind::Call(factory_address))
631 .data(Bytes::new())
632 .build()
633 .unwrap(),
634 )
635 .expect("call factory contract failed");
636
637 match &call_result {
638 ExecutionResult::Success { output, .. } => {
639 match output {
640 Output::Call(bytes) => {
641 if !bytes.is_empty() {
643 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
644 }
645 }
646 _ => panic!("unexpected output type"),
647 }
648 }
649 _ => panic!("execution result is not Success"),
650 }
651 }
652
653 #[test]
654 fn test_transact_many_with_transaction_index_error() {
655 use context::result::TransactionIndexedError;
656
657 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
658 let mut evm = ctx.build_mainnet();
659
660 let invalid_tx = TxEnv::builder()
662 .gas_limit(0) .build()
664 .unwrap();
665
666 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
668
669 let result = evm.transact_many([invalid_tx.clone()].into_iter());
671 assert!(matches!(
672 result,
673 Err(TransactionIndexedError {
674 transaction_index: 0,
675 ..
676 })
677 ));
678
679 let result = evm.transact_many([valid_tx, invalid_tx].into_iter());
681 assert!(matches!(
682 result,
683 Err(TransactionIndexedError {
684 transaction_index: 1,
685 ..
686 })
687 ));
688 }
689
690 #[test]
691 fn test_transact_many_success() {
692 use primitives::{address, U256};
693
694 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
695 let mut evm = ctx.build_mainnet();
696
697 let caller = address!("0x0000000000000000000000000000000000000001");
699 evm.db_mut().insert_account_info(
700 caller,
701 AccountInfo::new(
702 U256::from(1000000000000000000u64),
703 0,
704 B256::ZERO,
705 Bytecode::new(),
706 ),
707 );
708
709 let tx1 = TxEnv::builder()
711 .caller(caller)
712 .gas_limit(100000)
713 .gas_price(20_000_000_000u128)
714 .nonce(0)
715 .build()
716 .unwrap();
717
718 let tx2 = TxEnv::builder()
719 .caller(caller)
720 .gas_limit(100000)
721 .gas_price(20_000_000_000u128)
722 .nonce(1)
723 .build()
724 .unwrap();
725
726 let result = evm.transact_many([tx1, tx2].into_iter());
728 if let Err(e) = &result {
729 println!("Error: {e:?}");
730 }
731 let outputs = result.expect("All transactions should succeed");
732 assert_eq!(outputs.len(), 2);
733 }
734
735 #[test]
736 fn test_transact_many_finalize_with_error() {
737 use context::result::TransactionIndexedError;
738
739 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
740 let mut evm = ctx.build_mainnet();
741
742 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
744
745 let invalid_tx = TxEnv::builder()
746 .gas_limit(0) .build()
748 .unwrap();
749
750 let result = evm.transact_many_finalize([valid_tx, invalid_tx].into_iter());
752 assert!(matches!(
753 result,
754 Err(TransactionIndexedError {
755 transaction_index: 1,
756 ..
757 })
758 ));
759 }
760
761 #[test]
762 fn test_transact_many_commit_with_error() {
763 use context::result::TransactionIndexedError;
764
765 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
766 let mut evm = ctx.build_mainnet();
767
768 let invalid_tx = TxEnv::builder()
770 .gas_limit(0) .build()
772 .unwrap();
773
774 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
775
776 let result = evm.transact_many_commit([invalid_tx, valid_tx].into_iter());
778 assert!(matches!(
779 result,
780 Err(TransactionIndexedError {
781 transaction_index: 0,
782 ..
783 })
784 ));
785 }
786}