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}