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) -> Result<InitialAndFloorGas, InvalidTransaction> {
246 let mut gas = calculate_initial_tx_gas_for_tx(&tx, spec);
247
248 if is_eip7623_disabled {
249 gas.floor_gas = 0
250 }
251
252 if gas.initial_total_gas > tx.gas_limit() {
254 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
255 gas_limit: tx.gas_limit(),
256 initial_gas: gas.initial_total_gas,
257 });
258 }
259
260 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
263 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
264 gas_floor: gas.floor_gas,
265 gas_limit: tx.gas_limit(),
266 });
267 };
268
269 if is_amsterdam_eip8037_enabled && tx.gas_limit() > tx_gas_limit_cap {
273 let min_regular_gas = gas.initial_regular_gas().max(gas.floor_gas);
274 if min_regular_gas > tx_gas_limit_cap {
275 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
276 gas_floor: min_regular_gas,
277 gas_limit: tx_gas_limit_cap,
278 });
279 }
280 }
281
282 Ok(gas)
283}
284
285#[cfg(test)]
286mod tests {
287 use crate::{api::ExecuteEvm, ExecuteCommitEvm, MainBuilder, MainContext};
288 use bytecode::opcode;
289 use context::{
290 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
291 Context, ContextTr, TxEnv,
292 };
293 use database::{CacheDB, EmptyDB};
294 use primitives::{address, eip3860, eip7954, hardfork::SpecId, Bytes, TxKind, B256};
295 use state::{AccountInfo, Bytecode};
296
297 fn deploy_contract(
298 bytecode: Bytes,
299 spec_id: Option<SpecId>,
300 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
301 let ctx = Context::mainnet()
302 .modify_cfg_chained(|c| {
303 if let Some(spec_id) = spec_id {
304 c.set_spec_and_mainnet_gas_params(spec_id);
305 }
306 })
307 .with_db(CacheDB::<EmptyDB>::default());
308
309 let mut evm = ctx.build_mainnet();
310 evm.transact_commit(
311 TxEnv::builder()
312 .kind(TxKind::Create)
313 .data(bytecode.clone())
314 .build()
315 .unwrap(),
316 )
317 }
318
319 #[test]
320 fn test_eip3860_initcode_size_limit_failure() {
321 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
322 let bytecode: Bytes = large_bytecode.into();
323 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
324 assert!(matches!(
325 result,
326 Err(EVMError::Transaction(
327 InvalidTransaction::CreateInitCodeSizeLimit
328 ))
329 ));
330 }
331
332 #[test]
333 fn test_eip3860_initcode_size_limit_success_prague() {
334 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
335 let bytecode: Bytes = large_bytecode.into();
336 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
337 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
338 }
339
340 #[test]
341 fn test_eip7954_initcode_size_limit_failure_amsterdam() {
342 let large_bytecode = vec![opcode::STOP; eip7954::MAX_INITCODE_SIZE + 1];
343 let bytecode: Bytes = large_bytecode.into();
344 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
345 assert!(matches!(
346 result,
347 Err(EVMError::Transaction(
348 InvalidTransaction::CreateInitCodeSizeLimit
349 ))
350 ));
351 }
352
353 #[test]
354 fn test_eip7954_initcode_size_limit_success_amsterdam() {
355 let large_bytecode = vec![opcode::STOP; eip7954::MAX_INITCODE_SIZE];
356 let bytecode: Bytes = large_bytecode.into();
357 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
358 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
359 }
360
361 #[test]
362 fn test_eip7954_initcode_between_old_and_new_limit() {
363 let size = eip3860::MAX_INITCODE_SIZE + 1; let large_bytecode = vec![opcode::STOP; size];
367
368 let bytecode: Bytes = large_bytecode.clone().into();
370 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
371 assert!(matches!(
372 result,
373 Err(EVMError::Transaction(
374 InvalidTransaction::CreateInitCodeSizeLimit
375 ))
376 ));
377
378 let bytecode: Bytes = large_bytecode.into();
380 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
381 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
382 }
383
384 #[test]
385 fn test_eip7954_code_size_limit_failure() {
386 let init_code = vec![
392 0x62, 0x00, 0x80, 0x01, 0x60, 0x00, 0xf3, ];
396 let bytecode: Bytes = init_code.into();
397 let result = deploy_contract(bytecode, Some(SpecId::AMSTERDAM));
398 assert!(
399 matches!(
400 result,
401 Ok(ExecutionResult::Halt {
402 reason: HaltReason::CreateContractSizeLimit,
403 ..
404 },)
405 ),
406 "{result:?}"
407 );
408 }
409
410 #[test]
411 fn test_eip170_code_size_limit_failure() {
412 let init_code = vec![
417 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
421 let bytecode: Bytes = init_code.into();
422 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
423 assert!(
424 matches!(
425 result,
426 Ok(ExecutionResult::Halt {
427 reason: HaltReason::CreateContractSizeLimit,
428 ..
429 },)
430 ),
431 "{result:?}"
432 );
433 }
434
435 #[test]
436 fn test_eip170_code_size_limit_success() {
437 let init_code = vec![
442 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
446 let bytecode: Bytes = init_code.into();
447 let result = deploy_contract(bytecode, None);
448 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
449 }
450
451 #[test]
452 fn test_eip170_create_opcode_size_limit_failure() {
453 let factory_code = vec![
473 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
490
491 let factory_bytecode: Bytes = factory_code.into();
493 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
494 .expect("factory contract deployment failed");
495
496 let factory_address = match &factory_result {
498 ExecutionResult::Success {
499 output: Output::Create(_, Some(addr)),
500 ..
501 } => *addr,
502 _ => panic!("factory contract deployment failed: {factory_result:?}"),
503 };
504
505 let tx_caller = address!("0x0000000000000000000000000000000000100000");
507 let call_result = Context::mainnet()
508 .with_db(CacheDB::<EmptyDB>::default())
509 .build_mainnet()
510 .transact_commit(
511 TxEnv::builder()
512 .caller(tx_caller)
513 .kind(TxKind::Call(factory_address))
514 .data(Bytes::new())
515 .build()
516 .unwrap(),
517 )
518 .expect("call factory contract failed");
519
520 match &call_result {
521 ExecutionResult::Success { output, .. } => match output {
522 Output::Call(bytes) => {
523 if !bytes.is_empty() {
524 assert!(
525 bytes.iter().all(|&b| b == 0),
526 "When CREATE operation failed, it should return all zero address"
527 );
528 }
529 }
530 _ => panic!("unexpected output type"),
531 },
532 _ => panic!("execution result is not Success"),
533 }
534 }
535
536 #[test]
537 fn test_eip170_create_opcode_size_limit_success() {
538 let factory_code = vec![
558 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
575
576 let factory_bytecode: Bytes = factory_code.into();
578 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
579 .expect("factory contract deployment failed");
580 let factory_address = match &factory_result {
582 ExecutionResult::Success {
583 output: Output::Create(_, Some(addr)),
584 ..
585 } => *addr,
586 _ => panic!("factory contract deployment failed: {factory_result:?}"),
587 };
588
589 let tx_caller = address!("0x0000000000000000000000000000000000100000");
591 let call_result = Context::mainnet()
592 .with_db(CacheDB::<EmptyDB>::default())
593 .build_mainnet()
594 .transact_commit(
595 TxEnv::builder()
596 .caller(tx_caller)
597 .kind(TxKind::Call(factory_address))
598 .data(Bytes::new())
599 .build()
600 .unwrap(),
601 )
602 .expect("call factory contract failed");
603
604 match &call_result {
605 ExecutionResult::Success { output, .. } => {
606 match output {
607 Output::Call(bytes) => {
608 if !bytes.is_empty() {
610 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
611 }
612 }
613 _ => panic!("unexpected output type"),
614 }
615 }
616 _ => panic!("execution result is not Success"),
617 }
618 }
619
620 #[test]
621 fn test_transact_many_with_transaction_index_error() {
622 use context::result::TransactionIndexedError;
623
624 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
625 let mut evm = ctx.build_mainnet();
626
627 let invalid_tx = TxEnv::builder()
629 .gas_limit(0) .build()
631 .unwrap();
632
633 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
635
636 let result = evm.transact_many([invalid_tx.clone()].into_iter());
638 assert!(matches!(
639 result,
640 Err(TransactionIndexedError {
641 transaction_index: 0,
642 ..
643 })
644 ));
645
646 let result = evm.transact_many([valid_tx, invalid_tx].into_iter());
648 assert!(matches!(
649 result,
650 Err(TransactionIndexedError {
651 transaction_index: 1,
652 ..
653 })
654 ));
655 }
656
657 #[test]
658 fn test_transact_many_success() {
659 use primitives::{address, U256};
660
661 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
662 let mut evm = ctx.build_mainnet();
663
664 let caller = address!("0x0000000000000000000000000000000000000001");
666 evm.db_mut().insert_account_info(
667 caller,
668 AccountInfo::new(
669 U256::from(1000000000000000000u64),
670 0,
671 B256::ZERO,
672 Bytecode::new(),
673 ),
674 );
675
676 let tx1 = TxEnv::builder()
678 .caller(caller)
679 .gas_limit(100000)
680 .gas_price(20_000_000_000u128)
681 .nonce(0)
682 .build()
683 .unwrap();
684
685 let tx2 = TxEnv::builder()
686 .caller(caller)
687 .gas_limit(100000)
688 .gas_price(20_000_000_000u128)
689 .nonce(1)
690 .build()
691 .unwrap();
692
693 let result = evm.transact_many([tx1, tx2].into_iter());
695 if let Err(e) = &result {
696 println!("Error: {e:?}");
697 }
698 let outputs = result.expect("All transactions should succeed");
699 assert_eq!(outputs.len(), 2);
700 }
701
702 #[test]
703 fn test_transact_many_finalize_with_error() {
704 use context::result::TransactionIndexedError;
705
706 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
707 let mut evm = ctx.build_mainnet();
708
709 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
711
712 let invalid_tx = TxEnv::builder()
713 .gas_limit(0) .build()
715 .unwrap();
716
717 let result = evm.transact_many_finalize([valid_tx, invalid_tx].into_iter());
719 assert!(matches!(
720 result,
721 Err(TransactionIndexedError {
722 transaction_index: 1,
723 ..
724 })
725 ));
726 }
727
728 #[test]
729 fn test_transact_many_commit_with_error() {
730 use context::result::TransactionIndexedError;
731
732 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
733 let mut evm = ctx.build_mainnet();
734
735 let invalid_tx = TxEnv::builder()
737 .gas_limit(0) .build()
739 .unwrap();
740
741 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
742
743 let result = evm.transact_many_commit([invalid_tx, valid_tx].into_iter());
745 assert!(matches!(
746 result,
747 Err(TransactionIndexedError {
748 transaction_index: 0,
749 ..
750 })
751 ));
752 }
753}