revm_handler/
validation.rs

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
10/// Validates the execution environment including block and transaction parameters.
11pub fn validate_env<CTX: ContextTr, ERROR: From<InvalidHeader> + From<InvalidTransaction>>(
12    context: CTX,
13) -> Result<(), ERROR> {
14    let spec = context.cfg().spec().into();
15    // `prevrandao` is required for the merge
16    if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
17        return Err(InvalidHeader::PrevrandaoNotSet.into());
18    }
19    // `excess_blob_gas` is required for Cancun
20    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, InvalidTransaction>(context, spec).map_err(Into::into)
24}
25
26/// Validate transaction that has EIP-1559 priority fee
27pub 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        // Or gas_max_fee for eip1559
35        return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
36    }
37
38    // Check minimal cost against basefee
39    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
49/// Validate EIP-4844 transaction.
50pub 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    // Ensure that the user was willing to at least pay the current blob gasprice
57    if block_blob_gas_price > max_blob_fee {
58        return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
59    }
60
61    // There must be at least one blob
62    if blobs.is_empty() {
63        return Err(InvalidTransaction::EmptyBlobs);
64    }
65
66    // All versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG
67    for blob in blobs {
68        if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
69            return Err(InvalidTransaction::BlobVersionNotSupported);
70        }
71    }
72
73    // Ensure the total blob gas spent is at most equal to the limit
74    // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
75    if let Some(max_blobs) = max_blobs {
76        if blobs.len() > max_blobs as usize {
77            return Err(InvalidTransaction::TooManyBlobs {
78                have: blobs.len(),
79                max: max_blobs as usize,
80            });
81        }
82    }
83    Ok(())
84}
85
86/// Validate transaction against block and configuration for mainnet.
87pub fn validate_tx_env<CTX: ContextTr, Error>(
88    context: CTX,
89    spec_id: SpecId,
90) -> Result<(), InvalidTransaction> {
91    // Check if the transaction's chain id is correct
92    let tx_type = context.tx().tx_type();
93    let tx = context.tx();
94
95    let base_fee = if context.cfg().is_base_fee_check_disabled() {
96        None
97    } else {
98        Some(context.block().basefee() as u128)
99    };
100
101    let tx_type = TransactionType::from(tx_type);
102
103    // Check chain_id if config is enabled.
104    // EIP-155: Simple replay attack protection
105    if context.cfg().tx_chain_id_check() {
106        if let Some(chain_id) = tx.chain_id() {
107            if chain_id != context.cfg().chain_id() {
108                return Err(InvalidTransaction::InvalidChainId);
109            }
110        } else if !tx_type.is_legacy() && !tx_type.is_custom() {
111            // Legacy transaction are the only one that can omit chain_id.
112            return Err(InvalidTransaction::MissingChainId);
113        }
114    }
115
116    // EIP-7825: Transaction Gas Limit Cap
117    let cap = context.cfg().tx_gas_limit_cap();
118    if tx.gas_limit() > cap {
119        return Err(InvalidTransaction::TxGasLimitGreaterThanCap {
120            gas_limit: tx.gas_limit(),
121            cap,
122        });
123    }
124
125    let disable_priority_fee_check = context.cfg().is_priority_fee_check_disabled();
126
127    match tx_type {
128        TransactionType::Legacy => {
129            // Gas price must be at least the basefee.
130            if let Some(base_fee) = base_fee {
131                if tx.gas_price() < base_fee {
132                    return Err(InvalidTransaction::GasPriceLessThanBasefee);
133                }
134            }
135        }
136        TransactionType::Eip2930 => {
137            // Enabled in BERLIN hardfork
138            if !spec_id.is_enabled_in(SpecId::BERLIN) {
139                return Err(InvalidTransaction::Eip2930NotSupported);
140            }
141
142            // Gas price must be at least the basefee.
143            if let Some(base_fee) = base_fee {
144                if tx.gas_price() < base_fee {
145                    return Err(InvalidTransaction::GasPriceLessThanBasefee);
146                }
147            }
148        }
149        TransactionType::Eip1559 => {
150            if !spec_id.is_enabled_in(SpecId::LONDON) {
151                return Err(InvalidTransaction::Eip1559NotSupported);
152            }
153            validate_priority_fee_tx(
154                tx.max_fee_per_gas(),
155                tx.max_priority_fee_per_gas().unwrap_or_default(),
156                base_fee,
157                disable_priority_fee_check,
158            )?;
159        }
160        TransactionType::Eip4844 => {
161            if !spec_id.is_enabled_in(SpecId::CANCUN) {
162                return Err(InvalidTransaction::Eip4844NotSupported);
163            }
164
165            validate_priority_fee_tx(
166                tx.max_fee_per_gas(),
167                tx.max_priority_fee_per_gas().unwrap_or_default(),
168                base_fee,
169                disable_priority_fee_check,
170            )?;
171
172            validate_eip4844_tx(
173                tx.blob_versioned_hashes(),
174                tx.max_fee_per_blob_gas(),
175                context.block().blob_gasprice().unwrap_or_default(),
176                context.cfg().max_blobs_per_tx(),
177            )?;
178        }
179        TransactionType::Eip7702 => {
180            // Check if EIP-7702 transaction is enabled.
181            if !spec_id.is_enabled_in(SpecId::PRAGUE) {
182                return Err(InvalidTransaction::Eip7702NotSupported);
183            }
184
185            validate_priority_fee_tx(
186                tx.max_fee_per_gas(),
187                tx.max_priority_fee_per_gas().unwrap_or_default(),
188                base_fee,
189                disable_priority_fee_check,
190            )?;
191
192            let auth_list_len = tx.authorization_list_len();
193            // The transaction is considered invalid if the length of authorization_list is zero.
194            if auth_list_len == 0 {
195                return Err(InvalidTransaction::EmptyAuthorizationList);
196            }
197        }
198        /* // TODO(EOF) EOF removed from spec.
199        TransactionType::Eip7873 => {
200            // Check if EIP-7873 transaction is enabled.
201            if !spec_id.is_enabled_in(SpecId::OSAKA) {
202            return Err(InvalidTransaction::Eip7873NotSupported);
203            }
204            // validate chain id
205            if Some(context.cfg().chain_id()) != tx.chain_id() {
206                return Err(InvalidTransaction::InvalidChainId);
207            }
208
209            // validate initcodes.
210            validate_eip7873_initcodes(tx.initcodes())?;
211
212            // InitcodeTransaction is invalid if the to is nil.
213            if tx.kind().is_create() {
214                return Err(InvalidTransaction::Eip7873MissingTarget);
215            }
216
217            validate_priority_fee_tx(
218                tx.max_fee_per_gas(),
219                tx.max_priority_fee_per_gas().unwrap_or_default(),
220                base_fee,
221            )?;
222        }
223        */
224        TransactionType::Custom => {
225            // Custom transaction type check is not done here.
226        }
227    };
228
229    // Check if gas_limit is more than block_gas_limit
230    if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
231    {
232        return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
233    }
234
235    // EIP-3860: Limit and meter initcode. Still valid with EIP-7907 and increase of initcode size.
236    if spec_id.is_enabled_in(SpecId::SHANGHAI)
237        && tx.kind().is_create()
238        && context.tx().input().len() > context.cfg().max_initcode_size()
239    {
240        return Err(InvalidTransaction::CreateInitCodeSizeLimit);
241    }
242
243    Ok(())
244}
245
246/* TODO(EOF)
247/// Validate Initcode Transaction initcode list, return error if any of the following conditions are met:
248/// * there are zero entries in initcodes, or if there are more than MAX_INITCODE_COUNT entries.
249/// * any entry in initcodes is zero length, or if any entry exceeds MAX_INITCODE_SIZE.
250/// * the to is nil.
251pub fn validate_eip7873_initcodes(initcodes: &[Bytes]) -> Result<(), InvalidTransaction> {
252    let mut i = 0;
253    for initcode in initcodes {
254        // InitcodeTransaction is invalid if any entry in initcodes is zero length
255        if initcode.is_empty() {
256            return Err(InvalidTransaction::Eip7873EmptyInitcode { i });
257        }
258
259        // or if any entry exceeds MAX_INITCODE_SIZE.
260        if initcode.len() > MAX_INITCODE_SIZE {
261            return Err(InvalidTransaction::Eip7873InitcodeTooLarge {
262                i,
263                size: initcode.len(),
264            });
265        }
266
267        i += 1;
268    }
269
270    // InitcodeTransaction is invalid if there are zero entries in initcodes,
271    if i == 0 {
272        return Err(InvalidTransaction::Eip7873EmptyInitcodeList);
273    }
274
275    // or if there are more than MAX_INITCODE_COUNT entries.
276    if i > MAX_INITCODE_COUNT {
277        return Err(InvalidTransaction::Eip7873TooManyInitcodes { size: i });
278    }
279
280    Ok(())
281}
282*/
283
284/// Validate initial transaction gas.
285pub fn validate_initial_tx_gas(
286    tx: impl Transaction,
287    spec: SpecId,
288) -> Result<InitialAndFloorGas, InvalidTransaction> {
289    let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
290
291    // Additional check to see if limit is big enough to cover initial gas.
292    if gas.initial_gas > tx.gas_limit() {
293        return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
294            gas_limit: tx.gas_limit(),
295            initial_gas: gas.initial_gas,
296        });
297    }
298
299    // EIP-7623: Increase calldata cost
300    // floor gas should be less than gas limit.
301    if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
302        return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
303            gas_floor: gas.floor_gas,
304            gas_limit: tx.gas_limit(),
305        });
306    };
307
308    Ok(gas)
309}
310
311#[cfg(test)]
312mod tests {
313    use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
314    use bytecode::opcode;
315    use context::{
316        result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
317        Context, TxEnv,
318    };
319    use database::{CacheDB, EmptyDB};
320    use primitives::{address, eip3860, eip7907, hardfork::SpecId, Bytes, TxKind};
321
322    fn deploy_contract(
323        bytecode: Bytes,
324        spec_id: Option<SpecId>,
325    ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
326        let ctx = Context::mainnet()
327            .modify_cfg_chained(|c| {
328                if let Some(spec_id) = spec_id {
329                    c.spec = spec_id;
330                }
331            })
332            .with_db(CacheDB::<EmptyDB>::default());
333
334        let mut evm = ctx.build_mainnet();
335        evm.transact_commit(
336            TxEnv::builder()
337                .kind(TxKind::Create)
338                .data(bytecode.clone())
339                .build()
340                .unwrap(),
341        )
342    }
343
344    #[test]
345    fn test_eip3860_initcode_size_limit_failure() {
346        let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
347        let bytecode: Bytes = large_bytecode.into();
348        let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
349        assert!(matches!(
350            result,
351            Err(EVMError::Transaction(
352                InvalidTransaction::CreateInitCodeSizeLimit
353            ))
354        ));
355    }
356
357    #[test]
358    fn test_eip3860_initcode_size_limit_success_prague() {
359        let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
360        let bytecode: Bytes = large_bytecode.into();
361        let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
362        assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
363    }
364
365    #[test]
366    fn test_eip7907_initcode_size_limit_failure_osaka() {
367        let large_bytecode = vec![opcode::STOP; eip7907::MAX_INITCODE_SIZE + 1];
368        let bytecode: Bytes = large_bytecode.into();
369        let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
370        assert!(matches!(
371            result,
372            Err(EVMError::Transaction(
373                InvalidTransaction::CreateInitCodeSizeLimit
374            ))
375        ));
376    }
377
378    #[test]
379    fn test_eip7907_code_size_limit_failure() {
380        // EIP-7907: MAX_CODE_SIZE = 0x40000
381        // use the simplest method to return a contract code size greater than 0x40000
382        // PUSH3 0x40001 (greater than 0x40000) - return size
383        // PUSH1 0x00 - memory position 0
384        // RETURN - return uninitialized memory, will be filled with 0
385        let init_code = vec![
386            0x62, 0x04, 0x00, 0x01, // PUSH3 0x40001 (greater than 0x40000)
387            0x60, 0x00, // PUSH1 0
388            0xf3, // RETURN
389        ];
390        let bytecode: Bytes = init_code.into();
391        let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
392        assert!(matches!(
393            result,
394            Ok(ExecutionResult::Halt {
395                reason: HaltReason::CreateContractSizeLimit,
396                ..
397            },)
398        ));
399    }
400
401    #[test]
402    fn test_eip170_code_size_limit_failure() {
403        // use the simplest method to return a contract code size greater than 0x6000
404        // PUSH3 0x6001 (greater than 0x6000) - return size
405        // PUSH1 0x00 - memory position 0
406        // RETURN - return uninitialized memory, will be filled with 0
407        let init_code = vec![
408            0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (greater than 0x6000)
409            0x60, 0x00, // PUSH1 0
410            0xf3, // RETURN
411        ];
412        let bytecode: Bytes = init_code.into();
413        let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
414        assert!(matches!(
415            result,
416            Ok(ExecutionResult::Halt {
417                reason: HaltReason::CreateContractSizeLimit,
418                ..
419            },)
420        ));
421    }
422
423    #[test]
424    fn test_eip170_code_size_limit_success() {
425        // use the  simplest method to return a contract code size equal to 0x6000
426        // PUSH3 0x6000 - return size
427        // PUSH1 0x00 - memory position 0
428        // RETURN - return uninitialized memory, will be filled with 0
429        let init_code = vec![
430            0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000
431            0x60, 0x00, // PUSH1 0
432            0xf3, // RETURN
433        ];
434        let bytecode: Bytes = init_code.into();
435        let result = deploy_contract(bytecode, None);
436        assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
437    }
438
439    #[test]
440    fn test_eip170_create_opcode_size_limit_failure() {
441        // 1. create a "factory" contract, which will use the CREATE opcode to create another large contract
442        // 2. because the sub contract exceeds the EIP-170 limit, the CREATE operation should fail
443
444        // the bytecode of the factory contract:
445        // PUSH1 0x01      - the value for MSTORE
446        // PUSH1 0x00      - the memory position
447        // MSTORE          - store a non-zero value at the beginning of memory
448
449        // PUSH3 0x6001    - the return size (exceeds 0x6000)
450        // PUSH1 0x00      - the memory offset
451        // PUSH1 0x00      - the amount of ETH sent
452        // CREATE          - create contract instruction (create contract from current memory)
453
454        // PUSH1 0x00      - the return value storage position
455        // MSTORE          - store the address returned by CREATE to the memory position 0
456        // PUSH1 0x20      - the return size (32 bytes)
457        // PUSH1 0x00      - the return offset
458        // RETURN          - return the result
459
460        let factory_code = vec![
461            // 1. store a non-zero value at the beginning of memory
462            0x60, 0x01, // PUSH1 0x01
463            0x60, 0x00, // PUSH1 0x00
464            0x52, // MSTORE
465            // 2. prepare to create a large contract
466            0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (exceeds 0x6000)
467            0x60, 0x00, // PUSH1 0x00 (the memory offset)
468            0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
469            0xf0, // CREATE
470            // 3. store the address returned by CREATE to the memory position 0
471            0x60, 0x00, // PUSH1 0x00
472            0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
473            // 4. return the result
474            0x60, 0x20, // PUSH1 0x20 (32 bytes)
475            0x60, 0x00, // PUSH1 0x00
476            0xf3, // RETURN
477        ];
478
479        // deploy factory contract
480        let factory_bytecode: Bytes = factory_code.into();
481        let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
482            .expect("factory contract deployment failed");
483
484        // get factory contract address
485        let factory_address = match &factory_result {
486            ExecutionResult::Success {
487                output: Output::Create(_, Some(addr)),
488                ..
489            } => *addr,
490            _ => panic!("factory contract deployment failed: {factory_result:?}"),
491        };
492
493        // call factory contract to create sub contract
494        let tx_caller = address!("0x0000000000000000000000000000000000100000");
495        let call_result = Context::mainnet()
496            .with_db(CacheDB::<EmptyDB>::default())
497            .build_mainnet()
498            .transact_commit(
499                TxEnv::builder()
500                    .caller(tx_caller)
501                    .kind(TxKind::Call(factory_address))
502                    .data(Bytes::new())
503                    .build()
504                    .unwrap(),
505            )
506            .expect("call factory contract failed");
507
508        match &call_result {
509            ExecutionResult::Success { output, .. } => match output {
510                Output::Call(bytes) => {
511                    if !bytes.is_empty() {
512                        assert!(
513                            bytes.iter().all(|&b| b == 0),
514                            "When CREATE operation failed, it should return all zero address"
515                        );
516                    }
517                }
518                _ => panic!("unexpected output type"),
519            },
520            _ => panic!("execution result is not Success"),
521        }
522    }
523
524    #[test]
525    fn test_eip170_create_opcode_size_limit_success() {
526        // 1. create a "factory" contract, which will use the CREATE opcode to create another contract
527        // 2. the sub contract generated by the factory contract does not exceed the EIP-170 limit, so it should be created successfully
528
529        // the bytecode of the factory contract:
530        // PUSH1 0x01      - the value for MSTORE
531        // PUSH1 0x00      - the memory position
532        // MSTORE          - store a non-zero value at the beginning of memory
533
534        // PUSH3 0x6000    - the return size (0x6000)
535        // PUSH1 0x00      - the memory offset
536        // PUSH1 0x00      - the amount of ETH sent
537        // CREATE          - create contract instruction (create contract from current memory)
538
539        // PUSH1 0x00      - the return value storage position
540        // MSTORE          - store the address returned by CREATE to the memory position 0
541        // PUSH1 0x20      - the return size (32 bytes)
542        // PUSH1 0x00      - the return offset
543        // RETURN          - return the result
544
545        let factory_code = vec![
546            // 1. store a non-zero value at the beginning of memory
547            0x60, 0x01, // PUSH1 0x01
548            0x60, 0x00, // PUSH1 0x00
549            0x52, // MSTORE
550            // 2. prepare to create a contract
551            0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000 (0x6000)
552            0x60, 0x00, // PUSH1 0x00 (the memory offset)
553            0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
554            0xf0, // CREATE
555            // 3. store the address returned by CREATE to the memory position 0
556            0x60, 0x00, // PUSH1 0x00
557            0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
558            // 4. return the result
559            0x60, 0x20, // PUSH1 0x20 (32 bytes)
560            0x60, 0x00, // PUSH1 0x00
561            0xf3, // RETURN
562        ];
563
564        // deploy factory contract
565        let factory_bytecode: Bytes = factory_code.into();
566        let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
567            .expect("factory contract deployment failed");
568        // get factory contract address
569        let factory_address = match &factory_result {
570            ExecutionResult::Success {
571                output: Output::Create(_, Some(addr)),
572                ..
573            } => *addr,
574            _ => panic!("factory contract deployment failed: {factory_result:?}"),
575        };
576
577        // call factory contract to create sub contract
578        let tx_caller = address!("0x0000000000000000000000000000000000100000");
579        let call_result = Context::mainnet()
580            .with_db(CacheDB::<EmptyDB>::default())
581            .build_mainnet()
582            .transact_commit(
583                TxEnv::builder()
584                    .caller(tx_caller)
585                    .kind(TxKind::Call(factory_address))
586                    .data(Bytes::new())
587                    .build()
588                    .unwrap(),
589            )
590            .expect("call factory contract failed");
591
592        match &call_result {
593            ExecutionResult::Success { output, .. } => {
594                match output {
595                    Output::Call(bytes) => {
596                        // check if CREATE operation is successful (return non-zero address)
597                        if !bytes.is_empty() {
598                            assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
599                        }
600                    }
601                    _ => panic!("unexpected output type"),
602                }
603            }
604            _ => panic!("execution result is not Success"),
605        }
606    }
607}