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
239pub fn validate_initial_tx_gas(
244 tx: impl Transaction,
245 spec: SpecId,
246 is_eip7623_disabled: bool,
247 is_amsterdam_eip8037_enabled: bool,
248 tx_gas_limit_cap: u64,
249) -> Result<InitialAndFloorGas, InvalidTransaction> {
250 validate_initial_tx_gas_with_gas_params(
251 tx,
252 spec,
253 &GasParams::new_spec(spec),
254 is_eip7623_disabled,
255 is_amsterdam_eip8037_enabled,
256 tx_gas_limit_cap,
257 )
258}
259
260pub fn validate_initial_tx_gas_with_gas_params(
262 tx: impl Transaction,
263 spec: SpecId,
264 gas_params: &GasParams,
265 is_eip7623_disabled: bool,
266 is_amsterdam_eip8037_enabled: bool,
267 tx_gas_limit_cap: u64,
268) -> Result<InitialAndFloorGas, InvalidTransaction> {
269 let mut gas = gas_params.initial_tx_gas_for_tx(&tx);
270
271 if is_eip7623_disabled {
272 gas.set_floor_gas(0);
273 }
274
275 if !is_amsterdam_eip8037_enabled {
276 gas.set_initial_state_gas(0);
277 }
278
279 if gas.initial_total_gas() > tx.gas_limit() {
281 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
282 gas_limit: tx.gas_limit(),
283 initial_gas: gas.initial_total_gas(),
284 });
285 }
286
287 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas() > tx.gas_limit() {
290 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
291 gas_floor: gas.floor_gas(),
292 gas_limit: tx.gas_limit(),
293 });
294 };
295
296 if is_amsterdam_eip8037_enabled && tx.gas_limit() > tx_gas_limit_cap {
300 let min_regular_gas = gas.initial_regular_gas().max(gas.floor_gas());
301 if min_regular_gas > tx_gas_limit_cap {
302 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
303 gas_floor: min_regular_gas,
304 gas_limit: tx_gas_limit_cap,
305 });
306 }
307 }
308
309 Ok(gas)
310}
311
312#[cfg(test)]
313mod tests {
314 use crate::{api::ExecuteEvm, ExecuteCommitEvm, MainBuilder, MainContext};
315 use bytecode::opcode;
316 use context::{
317 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
318 Context, ContextTr, TxEnv,
319 };
320 use database::{CacheDB, EmptyDB};
321 use primitives::{address, eip3860, eip7954, hardfork::SpecId, Bytes, TxKind, B256};
322 use state::{AccountInfo, Bytecode};
323
324 fn deploy_contract(
325 bytecode: Bytes,
326 spec_id: Option<SpecId>,
327 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
328 let ctx = Context::mainnet()
329 .modify_cfg_chained(|c| {
330 if let Some(spec_id) = spec_id {
331 c.set_spec_and_mainnet_gas_params(spec_id);
332 }
333 })
334 .modify_block_chained(|block| block.gas_limit = 100_000_000)
335 .with_db(CacheDB::<EmptyDB>::default());
336
337 let mut evm = ctx.build_mainnet();
338 evm.transact_commit(
339 TxEnv::builder()
340 .kind(TxKind::Create)
341 .data(bytecode.clone())
342 .build()
343 .unwrap(),
344 )
345 }
346
347 #[test]
348 fn test_eip3860_initcode_size_limit_failure() {
349 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
350 let bytecode: Bytes = large_bytecode.into();
351 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
352 assert!(matches!(
353 result,
354 Err(EVMError::Transaction(
355 InvalidTransaction::CreateInitCodeSizeLimit
356 ))
357 ));
358 }
359
360 #[test]
361 fn test_eip3860_initcode_size_limit_success_prague() {
362 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
363 let bytecode: Bytes = large_bytecode.into();
364 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
365 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
366 }
367
368 #[test]
369 fn test_eip7954_initcode_size_limit_failure_amsterdam() {
370 let large_bytecode = vec![opcode::STOP; eip7954::MAX_INITCODE_SIZE + 1];
371 let bytecode: Bytes = large_bytecode.into();
372 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
373 assert!(matches!(
374 result,
375 Err(EVMError::Transaction(
376 InvalidTransaction::CreateInitCodeSizeLimit
377 ))
378 ));
379 }
380
381 #[test]
382 fn test_eip7954_initcode_size_limit_success_amsterdam() {
383 let large_bytecode = vec![opcode::STOP; eip7954::MAX_INITCODE_SIZE];
384 let bytecode: Bytes = large_bytecode.into();
385 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
386 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
387 }
388
389 #[test]
390 fn test_eip7954_initcode_between_old_and_new_limit() {
391 let size = eip3860::MAX_INITCODE_SIZE + 1; let large_bytecode = vec![opcode::STOP; size];
395
396 let bytecode: Bytes = large_bytecode.clone().into();
398 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
399 assert!(matches!(
400 result,
401 Err(EVMError::Transaction(
402 InvalidTransaction::CreateInitCodeSizeLimit
403 ))
404 ));
405
406 let bytecode: Bytes = large_bytecode.into();
408 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
409 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
410 }
411
412 #[test]
413 fn test_eip7954_code_size_limit_failure() {
414 let init_code = vec![
420 0x62, 0x00, 0x80, 0x01, 0x60, 0x00, 0xf3, ];
424 let bytecode: Bytes = init_code.into();
425 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
426 assert!(
427 matches!(
428 result,
429 Ok(ExecutionResult::Halt {
430 reason: HaltReason::CreateContractSizeLimit,
431 ..
432 },)
433 ),
434 "{result:?}"
435 );
436 }
437
438 #[test]
439 fn test_eip170_code_size_limit_failure() {
440 let init_code = vec![
445 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
449 let bytecode: Bytes = init_code.into();
450 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
451 assert!(
452 matches!(
453 result,
454 Ok(ExecutionResult::Halt {
455 reason: HaltReason::CreateContractSizeLimit,
456 ..
457 },)
458 ),
459 "{result:?}"
460 );
461 }
462
463 #[test]
464 fn test_eip170_code_size_limit_success() {
465 let init_code = vec![
470 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
474 let bytecode: Bytes = init_code.into();
475 let result = deploy_contract(bytecode, None);
476 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
477 }
478
479 #[test]
480 fn test_eip170_create_opcode_size_limit_failure() {
481 let factory_code = vec![
501 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
518
519 let factory_bytecode: Bytes = factory_code.into();
521 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
522 .expect("factory contract deployment failed");
523
524 let factory_address = match &factory_result {
526 ExecutionResult::Success {
527 output: Output::Create(_, Some(addr)),
528 ..
529 } => *addr,
530 _ => panic!("factory contract deployment failed: {factory_result:?}"),
531 };
532
533 let tx_caller = address!("0x0000000000000000000000000000000000100000");
535 let call_result = Context::mainnet()
536 .with_db(CacheDB::<EmptyDB>::default())
537 .build_mainnet()
538 .transact_commit(
539 TxEnv::builder()
540 .caller(tx_caller)
541 .kind(TxKind::Call(factory_address))
542 .data(Bytes::new())
543 .build()
544 .unwrap(),
545 )
546 .expect("call factory contract failed");
547
548 match &call_result {
549 ExecutionResult::Success { output, .. } => match output {
550 Output::Call(bytes) => {
551 if !bytes.is_empty() {
552 assert!(
553 bytes.iter().all(|&b| b == 0),
554 "When CREATE operation failed, it should return all zero address"
555 );
556 }
557 }
558 _ => panic!("unexpected output type"),
559 },
560 _ => panic!("execution result is not Success"),
561 }
562 }
563
564 #[test]
565 fn test_eip170_create_opcode_size_limit_success() {
566 let factory_code = vec![
586 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
603
604 let factory_bytecode: Bytes = factory_code.into();
606 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
607 .expect("factory contract deployment failed");
608 let factory_address = match &factory_result {
610 ExecutionResult::Success {
611 output: Output::Create(_, Some(addr)),
612 ..
613 } => *addr,
614 _ => panic!("factory contract deployment failed: {factory_result:?}"),
615 };
616
617 let tx_caller = address!("0x0000000000000000000000000000000000100000");
619 let call_result = Context::mainnet()
620 .with_db(CacheDB::<EmptyDB>::default())
621 .build_mainnet()
622 .transact_commit(
623 TxEnv::builder()
624 .caller(tx_caller)
625 .kind(TxKind::Call(factory_address))
626 .data(Bytes::new())
627 .build()
628 .unwrap(),
629 )
630 .expect("call factory contract failed");
631
632 match &call_result {
633 ExecutionResult::Success { output, .. } => {
634 match output {
635 Output::Call(bytes) => {
636 if !bytes.is_empty() {
638 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
639 }
640 }
641 _ => panic!("unexpected output type"),
642 }
643 }
644 _ => panic!("execution result is not Success"),
645 }
646 }
647
648 #[test]
649 fn test_transact_many_with_transaction_index_error() {
650 use context::result::TransactionIndexedError;
651
652 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
653 let mut evm = ctx.build_mainnet();
654
655 let invalid_tx = TxEnv::builder()
657 .gas_limit(0) .build()
659 .unwrap();
660
661 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
663
664 let result = evm.transact_many([invalid_tx.clone()].into_iter());
666 assert!(matches!(
667 result,
668 Err(TransactionIndexedError {
669 transaction_index: 0,
670 ..
671 })
672 ));
673
674 let result = evm.transact_many([valid_tx, invalid_tx].into_iter());
676 assert!(matches!(
677 result,
678 Err(TransactionIndexedError {
679 transaction_index: 1,
680 ..
681 })
682 ));
683 }
684
685 #[test]
686 fn test_transact_many_success() {
687 use primitives::{address, U256};
688
689 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
690 let mut evm = ctx.build_mainnet();
691
692 let caller = address!("0x0000000000000000000000000000000000000001");
694 evm.db_mut().insert_account_info(
695 caller,
696 AccountInfo::new(
697 U256::from(1000000000000000000u64),
698 0,
699 B256::ZERO,
700 Bytecode::new(),
701 ),
702 );
703
704 let tx1 = TxEnv::builder()
706 .caller(caller)
707 .gas_limit(100000)
708 .gas_price(20_000_000_000u128)
709 .nonce(0)
710 .build()
711 .unwrap();
712
713 let tx2 = TxEnv::builder()
714 .caller(caller)
715 .gas_limit(100000)
716 .gas_price(20_000_000_000u128)
717 .nonce(1)
718 .build()
719 .unwrap();
720
721 let result = evm.transact_many([tx1, tx2].into_iter());
723 if let Err(e) = &result {
724 println!("Error: {e:?}");
725 }
726 let outputs = result.expect("All transactions should succeed");
727 assert_eq!(outputs.len(), 2);
728 }
729
730 #[test]
731 fn test_transact_many_finalize_with_error() {
732 use context::result::TransactionIndexedError;
733
734 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
735 let mut evm = ctx.build_mainnet();
736
737 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
739
740 let invalid_tx = TxEnv::builder()
741 .gas_limit(0) .build()
743 .unwrap();
744
745 let result = evm.transact_many_finalize([valid_tx, invalid_tx].into_iter());
747 assert!(matches!(
748 result,
749 Err(TransactionIndexedError {
750 transaction_index: 1,
751 ..
752 })
753 ));
754 }
755
756 #[test]
757 fn test_transact_many_commit_with_error() {
758 use context::result::TransactionIndexedError;
759
760 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
761 let mut evm = ctx.build_mainnet();
762
763 let invalid_tx = TxEnv::builder()
765 .gas_limit(0) .build()
767 .unwrap();
768
769 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
770
771 let result = evm.transact_many_commit([invalid_tx, valid_tx].into_iter());
773 assert!(matches!(
774 result,
775 Err(TransactionIndexedError {
776 transaction_index: 0,
777 ..
778 })
779 ));
780 }
781}