1use context_interface::{
2 result::{InvalidHeader, InvalidTransaction},
3 transaction::{Transaction, TransactionType},
4 Block, Cfg, ContextTr,
5};
6use core::cmp;
7use interpreter::gas::{self, 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
26pub fn validate_priority_fee_tx(
28 max_fee: u128,
29 max_priority_fee: u128,
30 base_fee: Option<u128>,
31 disable_priority_fee_check: bool,
32) -> Result<(), InvalidTransaction> {
33 if !disable_priority_fee_check && max_priority_fee > max_fee {
34 return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
36 }
37
38 if let Some(base_fee) = base_fee {
40 let effective_gas_price = cmp::min(max_fee, base_fee.saturating_add(max_priority_fee));
41 if effective_gas_price < base_fee {
42 return Err(InvalidTransaction::GasPriceLessThanBasefee);
43 }
44 }
45
46 Ok(())
47}
48
49pub fn validate_eip4844_tx(
51 blobs: &[B256],
52 max_blob_fee: u128,
53 block_blob_gas_price: u128,
54 max_blobs: Option<u64>,
55) -> Result<(), InvalidTransaction> {
56 if block_blob_gas_price > max_blob_fee {
58 return Err(InvalidTransaction::BlobGasPriceGreaterThanMax {
59 block_blob_gas_price,
60 tx_max_fee_per_blob_gas: max_blob_fee,
61 });
62 }
63
64 if blobs.is_empty() {
66 return Err(InvalidTransaction::EmptyBlobs);
67 }
68
69 for blob in blobs {
71 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
72 return Err(InvalidTransaction::BlobVersionNotSupported);
73 }
74 }
75
76 if let Some(max_blobs) = max_blobs {
79 if blobs.len() > max_blobs as usize {
80 return Err(InvalidTransaction::TooManyBlobs {
81 have: blobs.len(),
82 max: max_blobs as usize,
83 });
84 }
85 }
86 Ok(())
87}
88
89pub fn validate_tx_env<CTX: ContextTr>(
91 context: CTX,
92 spec_id: SpecId,
93) -> Result<(), InvalidTransaction> {
94 let tx_type = context.tx().tx_type();
96 let tx = context.tx();
97
98 let base_fee = if context.cfg().is_base_fee_check_disabled() {
99 None
100 } else {
101 Some(context.block().basefee() as u128)
102 };
103
104 let tx_type = TransactionType::from(tx_type);
105
106 if context.cfg().tx_chain_id_check() {
109 if let Some(chain_id) = tx.chain_id() {
110 if chain_id != context.cfg().chain_id() {
111 return Err(InvalidTransaction::InvalidChainId);
112 }
113 } else if !tx_type.is_legacy() && !tx_type.is_custom() {
114 return Err(InvalidTransaction::MissingChainId);
116 }
117 }
118
119 let cap = context.cfg().tx_gas_limit_cap();
121 if tx.gas_limit() > cap {
122 return Err(InvalidTransaction::TxGasLimitGreaterThanCap {
123 gas_limit: tx.gas_limit(),
124 cap,
125 });
126 }
127
128 let disable_priority_fee_check = context.cfg().is_priority_fee_check_disabled();
129
130 match tx_type {
131 TransactionType::Legacy => {
132 if let Some(base_fee) = base_fee {
134 if tx.gas_price() < base_fee {
135 return Err(InvalidTransaction::GasPriceLessThanBasefee);
136 }
137 }
138 }
139 TransactionType::Eip2930 => {
140 if !spec_id.is_enabled_in(SpecId::BERLIN) {
142 return Err(InvalidTransaction::Eip2930NotSupported);
143 }
144
145 if let Some(base_fee) = base_fee {
147 if tx.gas_price() < base_fee {
148 return Err(InvalidTransaction::GasPriceLessThanBasefee);
149 }
150 }
151 }
152 TransactionType::Eip1559 => {
153 if !spec_id.is_enabled_in(SpecId::LONDON) {
154 return Err(InvalidTransaction::Eip1559NotSupported);
155 }
156 validate_priority_fee_tx(
157 tx.max_fee_per_gas(),
158 tx.max_priority_fee_per_gas().unwrap_or_default(),
159 base_fee,
160 disable_priority_fee_check,
161 )?;
162 }
163 TransactionType::Eip4844 => {
164 if !spec_id.is_enabled_in(SpecId::CANCUN) {
165 return Err(InvalidTransaction::Eip4844NotSupported);
166 }
167
168 validate_priority_fee_tx(
169 tx.max_fee_per_gas(),
170 tx.max_priority_fee_per_gas().unwrap_or_default(),
171 base_fee,
172 disable_priority_fee_check,
173 )?;
174
175 validate_eip4844_tx(
176 tx.blob_versioned_hashes(),
177 tx.max_fee_per_blob_gas(),
178 context.block().blob_gasprice().unwrap_or_default(),
179 context.cfg().max_blobs_per_tx(),
180 )?;
181 }
182 TransactionType::Eip7702 => {
183 if !spec_id.is_enabled_in(SpecId::PRAGUE) {
185 return Err(InvalidTransaction::Eip7702NotSupported);
186 }
187
188 validate_priority_fee_tx(
189 tx.max_fee_per_gas(),
190 tx.max_priority_fee_per_gas().unwrap_or_default(),
191 base_fee,
192 disable_priority_fee_check,
193 )?;
194
195 let auth_list_len = tx.authorization_list_len();
196 if auth_list_len == 0 {
198 return Err(InvalidTransaction::EmptyAuthorizationList);
199 }
200 }
201 TransactionType::Custom => {
202 }
204 };
205
206 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
208 {
209 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
210 }
211
212 if spec_id.is_enabled_in(SpecId::SHANGHAI)
214 && tx.kind().is_create()
215 && context.tx().input().len() > context.cfg().max_initcode_size()
216 {
217 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
218 }
219
220 Ok(())
221}
222
223pub fn validate_initial_tx_gas(
225 tx: impl Transaction,
226 spec: SpecId,
227 is_eip7623_disabled: bool,
228) -> Result<InitialAndFloorGas, InvalidTransaction> {
229 let mut gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
230
231 if is_eip7623_disabled {
232 gas.floor_gas = 0
233 }
234
235 if gas.initial_gas > tx.gas_limit() {
237 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
238 gas_limit: tx.gas_limit(),
239 initial_gas: gas.initial_gas,
240 });
241 }
242
243 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
246 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
247 gas_floor: gas.floor_gas,
248 gas_limit: tx.gas_limit(),
249 });
250 };
251
252 Ok(gas)
253}
254
255#[cfg(test)]
256mod tests {
257 use crate::{api::ExecuteEvm, ExecuteCommitEvm, MainBuilder, MainContext};
258 use bytecode::opcode;
259 use context::{
260 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
261 Context, ContextTr, TxEnv,
262 };
263 use database::{CacheDB, EmptyDB};
264 use primitives::{address, eip3860, eip7907, hardfork::SpecId, Bytes, TxKind, B256};
265 use state::{AccountInfo, Bytecode};
266
267 fn deploy_contract(
268 bytecode: Bytes,
269 spec_id: Option<SpecId>,
270 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
271 let ctx = Context::mainnet()
272 .modify_cfg_chained(|c| {
273 if let Some(spec_id) = spec_id {
274 c.spec = spec_id;
275 }
276 })
277 .with_db(CacheDB::<EmptyDB>::default());
278
279 let mut evm = ctx.build_mainnet();
280 evm.transact_commit(
281 TxEnv::builder()
282 .kind(TxKind::Create)
283 .data(bytecode.clone())
284 .build()
285 .unwrap(),
286 )
287 }
288
289 #[test]
290 fn test_eip3860_initcode_size_limit_failure() {
291 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
292 let bytecode: Bytes = large_bytecode.into();
293 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
294 assert!(matches!(
295 result,
296 Err(EVMError::Transaction(
297 InvalidTransaction::CreateInitCodeSizeLimit
298 ))
299 ));
300 }
301
302 #[test]
303 fn test_eip3860_initcode_size_limit_success_prague() {
304 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
305 let bytecode: Bytes = large_bytecode.into();
306 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
307 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
308 }
309
310 #[test]
311 fn test_eip7907_initcode_size_limit_failure_osaka() {
312 let large_bytecode = vec![opcode::STOP; eip7907::MAX_INITCODE_SIZE + 1];
313 let bytecode: Bytes = large_bytecode.into();
314 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
315 assert!(matches!(
316 result,
317 Err(EVMError::Transaction(
318 InvalidTransaction::CreateInitCodeSizeLimit
319 ))
320 ));
321 }
322
323 #[test]
324 fn test_eip7907_code_size_limit_failure() {
325 let init_code = vec![
331 0x62, 0x04, 0x00, 0x01, 0x60, 0x00, 0xf3, ];
335 let bytecode: Bytes = init_code.into();
336 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
337 assert!(
338 matches!(
339 result,
340 Ok(ExecutionResult::Halt {
341 reason: HaltReason::CreateContractSizeLimit,
342 ..
343 },)
344 ),
345 "{result:?}"
346 );
347 }
348
349 #[test]
350 fn test_eip170_code_size_limit_failure() {
351 let init_code = vec![
356 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
360 let bytecode: Bytes = init_code.into();
361 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
362 assert!(
363 matches!(
364 result,
365 Ok(ExecutionResult::Halt {
366 reason: HaltReason::CreateContractSizeLimit,
367 ..
368 },)
369 ),
370 "{result:?}"
371 );
372 }
373
374 #[test]
375 fn test_eip170_code_size_limit_success() {
376 let init_code = vec![
381 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
385 let bytecode: Bytes = init_code.into();
386 let result = deploy_contract(bytecode, None);
387 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
388 }
389
390 #[test]
391 fn test_eip170_create_opcode_size_limit_failure() {
392 let factory_code = vec![
412 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
429
430 let factory_bytecode: Bytes = factory_code.into();
432 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
433 .expect("factory contract deployment failed");
434
435 let factory_address = match &factory_result {
437 ExecutionResult::Success {
438 output: Output::Create(_, Some(addr)),
439 ..
440 } => *addr,
441 _ => panic!("factory contract deployment failed: {factory_result:?}"),
442 };
443
444 let tx_caller = address!("0x0000000000000000000000000000000000100000");
446 let call_result = Context::mainnet()
447 .with_db(CacheDB::<EmptyDB>::default())
448 .build_mainnet()
449 .transact_commit(
450 TxEnv::builder()
451 .caller(tx_caller)
452 .kind(TxKind::Call(factory_address))
453 .data(Bytes::new())
454 .build()
455 .unwrap(),
456 )
457 .expect("call factory contract failed");
458
459 match &call_result {
460 ExecutionResult::Success { output, .. } => match output {
461 Output::Call(bytes) => {
462 if !bytes.is_empty() {
463 assert!(
464 bytes.iter().all(|&b| b == 0),
465 "When CREATE operation failed, it should return all zero address"
466 );
467 }
468 }
469 _ => panic!("unexpected output type"),
470 },
471 _ => panic!("execution result is not Success"),
472 }
473 }
474
475 #[test]
476 fn test_eip170_create_opcode_size_limit_success() {
477 let factory_code = vec![
497 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
514
515 let factory_bytecode: Bytes = factory_code.into();
517 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
518 .expect("factory contract deployment failed");
519 let factory_address = match &factory_result {
521 ExecutionResult::Success {
522 output: Output::Create(_, Some(addr)),
523 ..
524 } => *addr,
525 _ => panic!("factory contract deployment failed: {factory_result:?}"),
526 };
527
528 let tx_caller = address!("0x0000000000000000000000000000000000100000");
530 let call_result = Context::mainnet()
531 .with_db(CacheDB::<EmptyDB>::default())
532 .build_mainnet()
533 .transact_commit(
534 TxEnv::builder()
535 .caller(tx_caller)
536 .kind(TxKind::Call(factory_address))
537 .data(Bytes::new())
538 .build()
539 .unwrap(),
540 )
541 .expect("call factory contract failed");
542
543 match &call_result {
544 ExecutionResult::Success { output, .. } => {
545 match output {
546 Output::Call(bytes) => {
547 if !bytes.is_empty() {
549 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
550 }
551 }
552 _ => panic!("unexpected output type"),
553 }
554 }
555 _ => panic!("execution result is not Success"),
556 }
557 }
558
559 #[test]
560 fn test_transact_many_with_transaction_index_error() {
561 use context::result::TransactionIndexedError;
562
563 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
564 let mut evm = ctx.build_mainnet();
565
566 let invalid_tx = TxEnv::builder()
568 .gas_limit(0) .build()
570 .unwrap();
571
572 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
574
575 let result = evm.transact_many([invalid_tx.clone()].into_iter());
577 assert!(matches!(
578 result,
579 Err(TransactionIndexedError {
580 transaction_index: 0,
581 ..
582 })
583 ));
584
585 let result = evm.transact_many([valid_tx, invalid_tx].into_iter());
587 assert!(matches!(
588 result,
589 Err(TransactionIndexedError {
590 transaction_index: 1,
591 ..
592 })
593 ));
594 }
595
596 #[test]
597 fn test_transact_many_success() {
598 use primitives::{address, U256};
599
600 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
601 let mut evm = ctx.build_mainnet();
602
603 let caller = address!("0x0000000000000000000000000000000000000001");
605 evm.db_mut().insert_account_info(
606 caller,
607 AccountInfo::new(
608 U256::from(1000000000000000000u64),
609 0,
610 B256::ZERO,
611 Bytecode::new(),
612 ),
613 );
614
615 let tx1 = TxEnv::builder()
617 .caller(caller)
618 .gas_limit(100000)
619 .gas_price(20_000_000_000u128)
620 .nonce(0)
621 .build()
622 .unwrap();
623
624 let tx2 = TxEnv::builder()
625 .caller(caller)
626 .gas_limit(100000)
627 .gas_price(20_000_000_000u128)
628 .nonce(1)
629 .build()
630 .unwrap();
631
632 let result = evm.transact_many([tx1, tx2].into_iter());
634 if let Err(e) = &result {
635 println!("Error: {e:?}");
636 }
637 let outputs = result.expect("All transactions should succeed");
638 assert_eq!(outputs.len(), 2);
639 }
640
641 #[test]
642 fn test_transact_many_finalize_with_error() {
643 use context::result::TransactionIndexedError;
644
645 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
646 let mut evm = ctx.build_mainnet();
647
648 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
650
651 let invalid_tx = TxEnv::builder()
652 .gas_limit(0) .build()
654 .unwrap();
655
656 let result = evm.transact_many_finalize([valid_tx, invalid_tx].into_iter());
658 assert!(matches!(
659 result,
660 Err(TransactionIndexedError {
661 transaction_index: 1,
662 ..
663 })
664 ));
665 }
666
667 #[test]
668 fn test_transact_many_commit_with_error() {
669 use context::result::TransactionIndexedError;
670
671 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
672 let mut evm = ctx.build_mainnet();
673
674 let invalid_tx = TxEnv::builder()
676 .gas_limit(0) .build()
678 .unwrap();
679
680 let valid_tx = TxEnv::builder().gas_limit(100000).build().unwrap();
681
682 let result = evm.transact_many_commit([invalid_tx, valid_tx].into_iter());
684 assert!(matches!(
685 result,
686 Err(TransactionIndexedError {
687 transaction_index: 0,
688 ..
689 })
690 ));
691 }
692}