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