1use context_interface::{
2 result::{InvalidHeader, InvalidTransaction},
3 transaction::{Transaction, TransactionType},
4 Block, Cfg, ContextTr,
5};
6use core::cmp;
7use interpreter::{instructions::calculate_initial_tx_gas_for_tx, InitialAndFloorGas};
8use primitives::{eip4844, hardfork::SpecId, B256};
9
10pub fn validate_env<CTX: ContextTr, ERROR: From<InvalidHeader> + From<InvalidTransaction>>(
12 context: CTX,
13) -> Result<(), ERROR> {
14 let spec = context.cfg().spec().into();
15 if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
17 return Err(InvalidHeader::PrevrandaoNotSet.into());
18 }
19 if spec.is_enabled_in(SpecId::CANCUN) && context.block().blob_excess_gas_and_price().is_none() {
21 return Err(InvalidHeader::ExcessBlobGasNotSet.into());
22 }
23 validate_tx_env::<CTX>(context, spec).map_err(Into::into)
24}
25
26#[inline]
28pub const fn validate_legacy_gas_price(
29 gas_price: u128,
30 base_fee: Option<u128>,
31) -> Result<(), InvalidTransaction> {
32 if let Some(base_fee) = base_fee {
34 if gas_price < base_fee {
35 return Err(InvalidTransaction::GasPriceLessThanBasefee);
36 }
37 }
38 Ok(())
39}
40
41pub fn validate_priority_fee_tx(
43 max_fee: u128,
44 max_priority_fee: u128,
45 base_fee: Option<u128>,
46 disable_priority_fee_check: bool,
47) -> Result<(), InvalidTransaction> {
48 if !disable_priority_fee_check && max_priority_fee > max_fee {
49 return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
51 }
52
53 if let Some(base_fee) = base_fee {
55 let effective_gas_price = cmp::min(max_fee, base_fee.saturating_add(max_priority_fee));
56 if effective_gas_price < base_fee {
57 return Err(InvalidTransaction::GasPriceLessThanBasefee);
58 }
59 }
60
61 Ok(())
62}
63
64#[inline]
66fn validate_priority_fee_for_tx<TX: Transaction>(
67 tx: TX,
68 base_fee: Option<u128>,
69 disable_priority_fee_check: bool,
70) -> Result<(), InvalidTransaction> {
71 validate_priority_fee_tx(
72 tx.max_fee_per_gas(),
73 tx.max_priority_fee_per_gas().unwrap_or_default(),
74 base_fee,
75 disable_priority_fee_check,
76 )
77}
78
79pub fn validate_eip4844_tx(
81 blobs: &[B256],
82 max_blob_fee: u128,
83 block_blob_gas_price: u128,
84 max_blobs: Option<u64>,
85) -> Result<(), InvalidTransaction> {
86 if block_blob_gas_price > max_blob_fee {
88 return Err(InvalidTransaction::BlobGasPriceGreaterThanMax {
89 block_blob_gas_price,
90 tx_max_fee_per_blob_gas: max_blob_fee,
91 });
92 }
93
94 if blobs.is_empty() {
96 return Err(InvalidTransaction::EmptyBlobs);
97 }
98
99 for blob in blobs {
101 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
102 return Err(InvalidTransaction::BlobVersionNotSupported);
103 }
104 }
105
106 if let Some(max_blobs) = max_blobs {
109 if blobs.len() > max_blobs as usize {
110 return Err(InvalidTransaction::TooManyBlobs {
111 have: blobs.len(),
112 max: max_blobs as usize,
113 });
114 }
115 }
116 Ok(())
117}
118
119pub fn validate_tx_env<CTX: ContextTr>(
121 context: CTX,
122 spec_id: SpecId,
123) -> Result<(), InvalidTransaction> {
124 let tx = context.tx();
126 let tx_type = tx.tx_type();
127
128 let base_fee = if context.cfg().is_base_fee_check_disabled() {
129 None
130 } else {
131 Some(context.block().basefee() as u128)
132 };
133
134 let tx_type = TransactionType::from(tx_type);
135
136 if context.cfg().tx_chain_id_check() {
139 if let Some(chain_id) = tx.chain_id() {
140 if chain_id != context.cfg().chain_id() {
141 return Err(InvalidTransaction::InvalidChainId);
142 }
143 } else if !tx_type.is_legacy() && !tx_type.is_custom() {
144 return Err(InvalidTransaction::MissingChainId);
146 }
147 }
148
149 if !context.cfg().is_amsterdam_eip8037_enabled() {
151 let cap = context.cfg().tx_gas_limit_cap();
153 if tx.gas_limit() > cap {
154 return Err(InvalidTransaction::TxGasLimitGreaterThanCap {
155 gas_limit: tx.gas_limit(),
156 cap,
157 });
158 }
159 }
160
161 let disable_priority_fee_check = context.cfg().is_priority_fee_check_disabled();
162
163 match tx_type {
164 TransactionType::Legacy => {
165 validate_legacy_gas_price(tx.gas_price(), base_fee)?;
166 }
167 TransactionType::Eip2930 => {
168 if !spec_id.is_enabled_in(SpecId::BERLIN) {
170 return Err(InvalidTransaction::Eip2930NotSupported);
171 }
172 validate_legacy_gas_price(tx.gas_price(), base_fee)?;
173 }
174 TransactionType::Eip1559 => {
175 if !spec_id.is_enabled_in(SpecId::LONDON) {
176 return Err(InvalidTransaction::Eip1559NotSupported);
177 }
178 validate_priority_fee_for_tx(tx, base_fee, disable_priority_fee_check)?;
179 }
180 TransactionType::Eip4844 => {
181 if !spec_id.is_enabled_in(SpecId::CANCUN) {
182 return Err(InvalidTransaction::Eip4844NotSupported);
183 }
184
185 validate_priority_fee_for_tx(tx, base_fee, disable_priority_fee_check)?;
186
187 validate_eip4844_tx(
188 tx.blob_versioned_hashes(),
189 tx.max_fee_per_blob_gas(),
190 context.block().blob_gasprice().unwrap_or_default(),
191 context.cfg().max_blobs_per_tx(),
192 )?;
193 }
194 TransactionType::Eip7702 => {
195 if !spec_id.is_enabled_in(SpecId::PRAGUE) {
197 return Err(InvalidTransaction::Eip7702NotSupported);
198 }
199
200 validate_priority_fee_for_tx(tx, base_fee, disable_priority_fee_check)?;
201
202 let auth_list_len = tx.authorization_list_len();
203 if auth_list_len == 0 {
205 return Err(InvalidTransaction::EmptyAuthorizationList);
206 }
207 }
208 TransactionType::Custom => {
209 }
211 };
212
213 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
217 {
218 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
219 }
220
221 if spec_id.is_enabled_in(SpecId::SHANGHAI)
223 && tx.kind().is_create()
224 && tx.input().len() > context.cfg().max_initcode_size()
225 {
226 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
227 }
228
229 if tx.nonce() == u64::MAX {
232 return Err(InvalidTransaction::NonceOverflowInTransaction);
233 }
234
235 Ok(())
236}
237
238pub fn validate_initial_tx_gas(
240 tx: impl Transaction,
241 spec: SpecId,
242 is_eip7623_disabled: bool,
243 is_amsterdam_eip8037_enabled: bool,
244 tx_gas_limit_cap: u64,
245 cpsb: u64,
246) -> Result<InitialAndFloorGas, InvalidTransaction> {
247 let mut gas = calculate_initial_tx_gas_for_tx(&tx, spec, cpsb);
248
249 if is_eip7623_disabled {
250 gas.set_floor_gas(0);
251 }
252
253 if gas.initial_total_gas() > tx.gas_limit() {
255 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
256 gas_limit: tx.gas_limit(),
257 initial_gas: gas.initial_total_gas(),
258 });
259 }
260
261 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas() > tx.gas_limit() {
264 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
265 gas_floor: gas.floor_gas(),
266 gas_limit: tx.gas_limit(),
267 });
268 };
269
270 if is_amsterdam_eip8037_enabled && tx.gas_limit() > tx_gas_limit_cap {
274 let min_regular_gas = gas.initial_regular_gas().max(gas.floor_gas());
275 if min_regular_gas > tx_gas_limit_cap {
276 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
277 gas_floor: min_regular_gas,
278 gas_limit: tx_gas_limit_cap,
279 });
280 }
281 }
282
283 Ok(gas)
284}
285
286#[cfg(test)]
287mod tests {
288 use crate::{api::ExecuteEvm, ExecuteCommitEvm, MainBuilder, MainContext};
289 use bytecode::opcode;
290 use context::{
291 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
292 Context, ContextTr, TxEnv,
293 };
294 use database::{CacheDB, EmptyDB};
295 use primitives::{address, eip3860, eip7954, hardfork::SpecId, Bytes, TxKind, B256};
296 use state::{AccountInfo, Bytecode};
297
298 fn deploy_contract(
299 bytecode: Bytes,
300 spec_id: Option<SpecId>,
301 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
302 let ctx = Context::mainnet()
303 .modify_cfg_chained(|c| {
304 if let Some(spec_id) = spec_id {
305 c.set_spec_and_mainnet_gas_params(spec_id);
306 }
307 })
308 .modify_block_chained(|block| block.gas_limit = 100_000_000)
309 .with_db(CacheDB::<EmptyDB>::default());
310
311 let mut evm = ctx.build_mainnet();
312 evm.transact_commit(
313 TxEnv::builder()
314 .kind(TxKind::Create)
315 .data(bytecode.clone())
316 .build()
317 .unwrap(),
318 )
319 }
320
321 #[test]
322 fn test_eip3860_initcode_size_limit_failure() {
323 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
324 let bytecode: Bytes = large_bytecode.into();
325 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
326 assert!(matches!(
327 result,
328 Err(EVMError::Transaction(
329 InvalidTransaction::CreateInitCodeSizeLimit
330 ))
331 ));
332 }
333
334 #[test]
335 fn test_eip3860_initcode_size_limit_success_prague() {
336 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
337 let bytecode: Bytes = large_bytecode.into();
338 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
339 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
340 }
341
342 #[test]
343 fn test_eip7954_initcode_size_limit_failure_amsterdam() {
344 let large_bytecode = vec![opcode::STOP; eip7954::MAX_INITCODE_SIZE + 1];
345 let bytecode: Bytes = large_bytecode.into();
346 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
347 assert!(matches!(
348 result,
349 Err(EVMError::Transaction(
350 InvalidTransaction::CreateInitCodeSizeLimit
351 ))
352 ));
353 }
354
355 #[test]
356 fn test_eip7954_initcode_size_limit_success_amsterdam() {
357 let large_bytecode = vec![opcode::STOP; eip7954::MAX_INITCODE_SIZE];
358 let bytecode: Bytes = large_bytecode.into();
359 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
360 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
361 }
362
363 #[test]
364 fn test_eip7954_initcode_between_old_and_new_limit() {
365 let size = eip3860::MAX_INITCODE_SIZE + 1; let large_bytecode = vec![opcode::STOP; size];
369
370 let bytecode: Bytes = large_bytecode.clone().into();
372 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
373 assert!(matches!(
374 result,
375 Err(EVMError::Transaction(
376 InvalidTransaction::CreateInitCodeSizeLimit
377 ))
378 ));
379
380 let bytecode: Bytes = large_bytecode.into();
382 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
383 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
384 }
385
386 #[test]
387 fn test_eip7954_code_size_limit_failure() {
388 let init_code = vec![
394 0x62, 0x00, 0x80, 0x01, 0x60, 0x00, 0xf3, ];
398 let bytecode: Bytes = init_code.into();
399 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
400 assert!(
401 matches!(
402 result,
403 Ok(ExecutionResult::Halt {
404 reason: HaltReason::CreateContractSizeLimit,
405 ..
406 },)
407 ),
408 "{result:?}"
409 );
410 }
411
412 #[test]
413 fn test_eip170_code_size_limit_failure() {
414 let init_code = vec![
419 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
423 let bytecode: Bytes = init_code.into();
424 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
425 assert!(
426 matches!(
427 result,
428 Ok(ExecutionResult::Halt {
429 reason: HaltReason::CreateContractSizeLimit,
430 ..
431 },)
432 ),
433 "{result:?}"
434 );
435 }
436
437 #[test]
438 fn test_eip170_code_size_limit_success() {
439 let init_code = vec![
444 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
448 let bytecode: Bytes = init_code.into();
449 let result = deploy_contract(bytecode, None);
450 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
451 }
452
453 #[test]
454 fn test_eip170_create_opcode_size_limit_failure() {
455 let factory_code = vec![
475 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
492
493 let factory_bytecode: Bytes = factory_code.into();
495 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
496 .expect("factory contract deployment failed");
497
498 let factory_address = match &factory_result {
500 ExecutionResult::Success {
501 output: Output::Create(_, Some(addr)),
502 ..
503 } => *addr,
504 _ => panic!("factory contract deployment failed: {factory_result:?}"),
505 };
506
507 let tx_caller = address!("0x0000000000000000000000000000000000100000");
509 let call_result = Context::mainnet()
510 .with_db(CacheDB::<EmptyDB>::default())
511 .build_mainnet()
512 .transact_commit(
513 TxEnv::builder()
514 .caller(tx_caller)
515 .kind(TxKind::Call(factory_address))
516 .data(Bytes::new())
517 .build()
518 .unwrap(),
519 )
520 .expect("call factory contract failed");
521
522 match &call_result {
523 ExecutionResult::Success { output, .. } => match output {
524 Output::Call(bytes) => {
525 if !bytes.is_empty() {
526 assert!(
527 bytes.iter().all(|&b| b == 0),
528 "When CREATE operation failed, it should return all zero address"
529 );
530 }
531 }
532 _ => panic!("unexpected output type"),
533 },
534 _ => panic!("execution result is not Success"),
535 }
536 }
537
538 #[test]
539 fn test_eip170_create_opcode_size_limit_success() {
540 let factory_code = vec![
560 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
577
578 let factory_bytecode: Bytes = factory_code.into();
580 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
581 .expect("factory contract deployment failed");
582 let factory_address = match &factory_result {
584 ExecutionResult::Success {
585 output: Output::Create(_, Some(addr)),
586 ..
587 } => *addr,
588 _ => panic!("factory contract deployment failed: {factory_result:?}"),
589 };
590
591 let tx_caller = address!("0x0000000000000000000000000000000000100000");
593 let call_result = Context::mainnet()
594 .with_db(CacheDB::<EmptyDB>::default())
595 .build_mainnet()
596 .transact_commit(
597 TxEnv::builder()
598 .caller(tx_caller)
599 .kind(TxKind::Call(factory_address))
600 .data(Bytes::new())
601 .build()
602 .unwrap(),
603 )
604 .expect("call factory contract failed");
605
606 match &call_result {
607 ExecutionResult::Success { output, .. } => {
608 match output {
609 Output::Call(bytes) => {
610 if !bytes.is_empty() {
612 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
613 }
614 }
615 _ => panic!("unexpected output type"),
616 }
617 }
618 _ => panic!("execution result is not Success"),
619 }
620 }
621
622 #[test]
623 fn test_transact_many_with_transaction_index_error() {
624 use context::result::TransactionIndexedError;
625
626 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
627 let mut evm = ctx.build_mainnet();
628
629 let invalid_tx = TxEnv::builder()
631 .gas_limit(0) .build()
633 .unwrap();
634
635 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
637
638 let result = evm.transact_many([invalid_tx.clone()].into_iter());
640 assert!(matches!(
641 result,
642 Err(TransactionIndexedError {
643 transaction_index: 0,
644 ..
645 })
646 ));
647
648 let result = evm.transact_many([valid_tx, invalid_tx].into_iter());
650 assert!(matches!(
651 result,
652 Err(TransactionIndexedError {
653 transaction_index: 1,
654 ..
655 })
656 ));
657 }
658
659 #[test]
660 fn test_transact_many_success() {
661 use primitives::{address, U256};
662
663 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
664 let mut evm = ctx.build_mainnet();
665
666 let caller = address!("0x0000000000000000000000000000000000000001");
668 evm.db_mut().insert_account_info(
669 caller,
670 AccountInfo::new(
671 U256::from(1000000000000000000u64),
672 0,
673 B256::ZERO,
674 Bytecode::new(),
675 ),
676 );
677
678 let tx1 = TxEnv::builder()
680 .caller(caller)
681 .gas_limit(100000)
682 .gas_price(20_000_000_000u128)
683 .nonce(0)
684 .build()
685 .unwrap();
686
687 let tx2 = TxEnv::builder()
688 .caller(caller)
689 .gas_limit(100000)
690 .gas_price(20_000_000_000u128)
691 .nonce(1)
692 .build()
693 .unwrap();
694
695 let result = evm.transact_many([tx1, tx2].into_iter());
697 if let Err(e) = &result {
698 println!("Error: {e:?}");
699 }
700 let outputs = result.expect("All transactions should succeed");
701 assert_eq!(outputs.len(), 2);
702 }
703
704 #[test]
705 fn test_transact_many_finalize_with_error() {
706 use context::result::TransactionIndexedError;
707
708 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
709 let mut evm = ctx.build_mainnet();
710
711 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
713
714 let invalid_tx = TxEnv::builder()
715 .gas_limit(0) .build()
717 .unwrap();
718
719 let result = evm.transact_many_finalize([valid_tx, invalid_tx].into_iter());
721 assert!(matches!(
722 result,
723 Err(TransactionIndexedError {
724 transaction_index: 1,
725 ..
726 })
727 ));
728 }
729
730 #[test]
731 fn test_transact_many_commit_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 invalid_tx = TxEnv::builder()
739 .gas_limit(0) .build()
741 .unwrap();
742
743 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
744
745 let result = evm.transact_many_commit([invalid_tx, valid_tx].into_iter());
747 assert!(matches!(
748 result,
749 Err(TransactionIndexedError {
750 transaction_index: 0,
751 ..
752 })
753 ));
754 }
755}