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_tx_chained(|tx| {
328                tx.kind = TxKind::Create;
329                tx.data = bytecode.clone();
330            })
331            .modify_cfg_chained(|c| {
332                if let Some(spec_id) = spec_id {
333                    c.spec = spec_id;
334                }
335            })
336            .with_db(CacheDB::<EmptyDB>::default());
337
338        let mut evm = ctx.build_mainnet();
339        evm.transact_commit(TxEnv {
340            kind: TxKind::Create,
341            data: bytecode.clone(),
342            ..Default::default()
343        })
344    }
345
346    #[test]
347    fn test_eip3860_initcode_size_limit_failure() {
348        let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
349        let bytecode: Bytes = large_bytecode.into();
350        let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
351        assert!(matches!(
352            result,
353            Err(EVMError::Transaction(
354                InvalidTransaction::CreateInitCodeSizeLimit
355            ))
356        ));
357    }
358
359    #[test]
360    fn test_eip3860_initcode_size_limit_success_prague() {
361        let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
362        let bytecode: Bytes = large_bytecode.into();
363        let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
364        assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
365    }
366
367    #[test]
368    fn test_eip7907_initcode_size_limit_failure_osaka() {
369        let large_bytecode = vec![opcode::STOP; eip7907::MAX_INITCODE_SIZE + 1];
370        let bytecode: Bytes = large_bytecode.into();
371        let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
372        assert!(matches!(
373            result,
374            Err(EVMError::Transaction(
375                InvalidTransaction::CreateInitCodeSizeLimit
376            ))
377        ));
378    }
379
380    #[test]
381    fn test_eip7907_code_size_limit_failure() {
382        // EIP-7907: MAX_CODE_SIZE = 0x40000
383        // use the simplest method to return a contract code size greater than 0x40000
384        // PUSH3 0x40001 (greater than 0x40000) - return size
385        // PUSH1 0x00 - memory position 0
386        // RETURN - return uninitialized memory, will be filled with 0
387        let init_code = vec![
388            0x62, 0x04, 0x00, 0x01, // PUSH3 0x40001 (greater than 0x40000)
389            0x60, 0x00, // PUSH1 0
390            0xf3, // RETURN
391        ];
392        let bytecode: Bytes = init_code.into();
393        let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
394        assert!(matches!(
395            result,
396            Ok(ExecutionResult::Halt {
397                reason: HaltReason::CreateContractSizeLimit,
398                ..
399            },)
400        ));
401    }
402
403    #[test]
404    fn test_eip170_code_size_limit_failure() {
405        // use the simplest method to return a contract code size greater than 0x6000
406        // PUSH3 0x6001 (greater than 0x6000) - return size
407        // PUSH1 0x00 - memory position 0
408        // RETURN - return uninitialized memory, will be filled with 0
409        let init_code = vec![
410            0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (greater than 0x6000)
411            0x60, 0x00, // PUSH1 0
412            0xf3, // RETURN
413        ];
414        let bytecode: Bytes = init_code.into();
415        let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
416        assert!(matches!(
417            result,
418            Ok(ExecutionResult::Halt {
419                reason: HaltReason::CreateContractSizeLimit,
420                ..
421            },)
422        ));
423    }
424
425    #[test]
426    fn test_eip170_code_size_limit_success() {
427        // use the  simplest method to return a contract code size equal to 0x6000
428        // PUSH3 0x6000 - return size
429        // PUSH1 0x00 - memory position 0
430        // RETURN - return uninitialized memory, will be filled with 0
431        let init_code = vec![
432            0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000
433            0x60, 0x00, // PUSH1 0
434            0xf3, // RETURN
435        ];
436        let bytecode: Bytes = init_code.into();
437        let result = deploy_contract(bytecode, None);
438        assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
439    }
440
441    #[test]
442    fn test_eip170_create_opcode_size_limit_failure() {
443        // 1. create a "factory" contract, which will use the CREATE opcode to create another large contract
444        // 2. because the sub contract exceeds the EIP-170 limit, the CREATE operation should fail
445
446        // the bytecode of the factory contract:
447        // PUSH1 0x01      - the value for MSTORE
448        // PUSH1 0x00      - the memory position
449        // MSTORE          - store a non-zero value at the beginning of memory
450
451        // PUSH3 0x6001    - the return size (exceeds 0x6000)
452        // PUSH1 0x00      - the memory offset
453        // PUSH1 0x00      - the amount of ETH sent
454        // CREATE          - create contract instruction (create contract from current memory)
455
456        // PUSH1 0x00      - the return value storage position
457        // MSTORE          - store the address returned by CREATE to the memory position 0
458        // PUSH1 0x20      - the return size (32 bytes)
459        // PUSH1 0x00      - the return offset
460        // RETURN          - return the result
461
462        let factory_code = vec![
463            // 1. store a non-zero value at the beginning of memory
464            0x60, 0x01, // PUSH1 0x01
465            0x60, 0x00, // PUSH1 0x00
466            0x52, // MSTORE
467            // 2. prepare to create a large contract
468            0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (exceeds 0x6000)
469            0x60, 0x00, // PUSH1 0x00 (the memory offset)
470            0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
471            0xf0, // CREATE
472            // 3. store the address returned by CREATE to the memory position 0
473            0x60, 0x00, // PUSH1 0x00
474            0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
475            // 4. return the result
476            0x60, 0x20, // PUSH1 0x20 (32 bytes)
477            0x60, 0x00, // PUSH1 0x00
478            0xf3, // RETURN
479        ];
480
481        // deploy factory contract
482        let factory_bytecode: Bytes = factory_code.into();
483        let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
484            .expect("factory contract deployment failed");
485
486        // get factory contract address
487        let factory_address = match &factory_result {
488            ExecutionResult::Success {
489                output: Output::Create(_, Some(addr)),
490                ..
491            } => *addr,
492            _ => panic!("factory contract deployment failed: {factory_result:?}"),
493        };
494
495        // call factory contract to create sub contract
496        let tx_caller = address!("0x0000000000000000000000000000000000100000");
497        let call_result = Context::mainnet()
498            .with_db(CacheDB::<EmptyDB>::default())
499            .build_mainnet()
500            .transact_commit(TxEnv {
501                caller: tx_caller,
502                kind: TxKind::Call(factory_address),
503                data: Bytes::new(),
504                ..Default::default()
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(TxEnv {
583                caller: tx_caller,
584                kind: TxKind::Call(factory_address),
585                data: Bytes::new(),
586                ..Default::default()
587            })
588            .expect("call factory contract failed");
589
590        match &call_result {
591            ExecutionResult::Success { output, .. } => {
592                match output {
593                    Output::Call(bytes) => {
594                        // check if CREATE operation is successful (return non-zero address)
595                        if !bytes.is_empty() {
596                            assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
597                        }
598                    }
599                    _ => panic!("unexpected output type"),
600                }
601            }
602            _ => panic!("execution result is not Success"),
603        }
604    }
605}