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>(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            block_blob_gas_price,
60            tx_max_fee_per_blob_gas: max_blob_fee,
61        });
62    }
63
64    // There must be at least one blob
65    if blobs.is_empty() {
66        return Err(InvalidTransaction::EmptyBlobs);
67    }
68
69    // All versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG
70    for blob in blobs {
71        if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
72            return Err(InvalidTransaction::BlobVersionNotSupported);
73        }
74    }
75
76    // Ensure the total blob gas spent is at most equal to the limit
77    // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
78    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
89/// Validate transaction against block and configuration for mainnet.
90pub fn validate_tx_env<CTX: ContextTr>(
91    context: CTX,
92    spec_id: SpecId,
93) -> Result<(), InvalidTransaction> {
94    // Check if the transaction's chain id is correct
95    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    // Check chain_id if config is enabled.
107    // EIP-155: Simple replay attack protection
108    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            // Legacy transaction are the only one that can omit chain_id.
115            return Err(InvalidTransaction::MissingChainId);
116        }
117    }
118
119    // EIP-7825: Transaction Gas Limit Cap
120    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            // Gas price must be at least the basefee.
133            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            // Enabled in BERLIN hardfork
141            if !spec_id.is_enabled_in(SpecId::BERLIN) {
142                return Err(InvalidTransaction::Eip2930NotSupported);
143            }
144
145            // Gas price must be at least the basefee.
146            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            // Check if EIP-7702 transaction is enabled.
184            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            // The transaction is considered invalid if the length of authorization_list is zero.
197            if auth_list_len == 0 {
198                return Err(InvalidTransaction::EmptyAuthorizationList);
199            }
200        }
201        TransactionType::Custom => {
202            // Custom transaction type check is not done here.
203        }
204    };
205
206    // Check if gas_limit is more than block_gas_limit
207    if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
208    {
209        return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
210    }
211
212    // EIP-3860: Limit and meter initcode. Still valid with EIP-7907 and increase of initcode size.
213    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
223/// Validate initial transaction gas.
224pub fn validate_initial_tx_gas(
225    tx: impl Transaction,
226    spec: SpecId,
227) -> Result<InitialAndFloorGas, InvalidTransaction> {
228    let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
229
230    // Additional check to see if limit is big enough to cover initial gas.
231    if gas.initial_gas > tx.gas_limit() {
232        return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
233            gas_limit: tx.gas_limit(),
234            initial_gas: gas.initial_gas,
235        });
236    }
237
238    // EIP-7623: Increase calldata cost
239    // floor gas should be less than gas limit.
240    if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
241        return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
242            gas_floor: gas.floor_gas,
243            gas_limit: tx.gas_limit(),
244        });
245    };
246
247    Ok(gas)
248}
249
250#[cfg(test)]
251mod tests {
252    use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
253    use bytecode::opcode;
254    use context::{
255        result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
256        Context, TxEnv,
257    };
258    use database::{CacheDB, EmptyDB};
259    use primitives::{address, eip3860, eip7907, hardfork::SpecId, Bytes, TxKind};
260
261    fn deploy_contract(
262        bytecode: Bytes,
263        spec_id: Option<SpecId>,
264    ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
265        let ctx = Context::mainnet()
266            .modify_cfg_chained(|c| {
267                if let Some(spec_id) = spec_id {
268                    c.spec = spec_id;
269                }
270            })
271            .with_db(CacheDB::<EmptyDB>::default());
272
273        let mut evm = ctx.build_mainnet();
274        evm.transact_commit(
275            TxEnv::builder()
276                .kind(TxKind::Create)
277                .data(bytecode.clone())
278                .build()
279                .unwrap(),
280        )
281    }
282
283    #[test]
284    fn test_eip3860_initcode_size_limit_failure() {
285        let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
286        let bytecode: Bytes = large_bytecode.into();
287        let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
288        assert!(matches!(
289            result,
290            Err(EVMError::Transaction(
291                InvalidTransaction::CreateInitCodeSizeLimit
292            ))
293        ));
294    }
295
296    #[test]
297    fn test_eip3860_initcode_size_limit_success_prague() {
298        let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
299        let bytecode: Bytes = large_bytecode.into();
300        let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
301        assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
302    }
303
304    #[test]
305    fn test_eip7907_initcode_size_limit_failure_osaka() {
306        let large_bytecode = vec![opcode::STOP; eip7907::MAX_INITCODE_SIZE + 1];
307        let bytecode: Bytes = large_bytecode.into();
308        let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
309        assert!(matches!(
310            result,
311            Err(EVMError::Transaction(
312                InvalidTransaction::CreateInitCodeSizeLimit
313            ))
314        ));
315    }
316
317    #[test]
318    fn test_eip7907_code_size_limit_failure() {
319        // EIP-7907: MAX_CODE_SIZE = 0x40000
320        // use the simplest method to return a contract code size greater than 0x40000
321        // PUSH3 0x40001 (greater than 0x40000) - return size
322        // PUSH1 0x00 - memory position 0
323        // RETURN - return uninitialized memory, will be filled with 0
324        let init_code = vec![
325            0x62, 0x04, 0x00, 0x01, // PUSH3 0x40001 (greater than 0x40000)
326            0x60, 0x00, // PUSH1 0
327            0xf3, // RETURN
328        ];
329        let bytecode: Bytes = init_code.into();
330        let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
331        assert!(
332            matches!(
333                result,
334                Ok(ExecutionResult::Halt {
335                    reason: HaltReason::CreateContractSizeLimit,
336                    ..
337                },)
338            ),
339            "{result:?}"
340        );
341    }
342
343    #[test]
344    fn test_eip170_code_size_limit_failure() {
345        // use the simplest method to return a contract code size greater than 0x6000
346        // PUSH3 0x6001 (greater than 0x6000) - return size
347        // PUSH1 0x00 - memory position 0
348        // RETURN - return uninitialized memory, will be filled with 0
349        let init_code = vec![
350            0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (greater than 0x6000)
351            0x60, 0x00, // PUSH1 0
352            0xf3, // RETURN
353        ];
354        let bytecode: Bytes = init_code.into();
355        let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
356        assert!(
357            matches!(
358                result,
359                Ok(ExecutionResult::Halt {
360                    reason: HaltReason::CreateContractSizeLimit,
361                    ..
362                },)
363            ),
364            "{result:?}"
365        );
366    }
367
368    #[test]
369    fn test_eip170_code_size_limit_success() {
370        // use the  simplest method to return a contract code size equal to 0x6000
371        // PUSH3 0x6000 - return size
372        // PUSH1 0x00 - memory position 0
373        // RETURN - return uninitialized memory, will be filled with 0
374        let init_code = vec![
375            0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000
376            0x60, 0x00, // PUSH1 0
377            0xf3, // RETURN
378        ];
379        let bytecode: Bytes = init_code.into();
380        let result = deploy_contract(bytecode, None);
381        assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
382    }
383
384    #[test]
385    fn test_eip170_create_opcode_size_limit_failure() {
386        // 1. create a "factory" contract, which will use the CREATE opcode to create another large contract
387        // 2. because the sub contract exceeds the EIP-170 limit, the CREATE operation should fail
388
389        // the bytecode of the factory contract:
390        // PUSH1 0x01      - the value for MSTORE
391        // PUSH1 0x00      - the memory position
392        // MSTORE          - store a non-zero value at the beginning of memory
393
394        // PUSH3 0x6001    - the return size (exceeds 0x6000)
395        // PUSH1 0x00      - the memory offset
396        // PUSH1 0x00      - the amount of ETH sent
397        // CREATE          - create contract instruction (create contract from current memory)
398
399        // PUSH1 0x00      - the return value storage position
400        // MSTORE          - store the address returned by CREATE to the memory position 0
401        // PUSH1 0x20      - the return size (32 bytes)
402        // PUSH1 0x00      - the return offset
403        // RETURN          - return the result
404
405        let factory_code = vec![
406            // 1. store a non-zero value at the beginning of memory
407            0x60, 0x01, // PUSH1 0x01
408            0x60, 0x00, // PUSH1 0x00
409            0x52, // MSTORE
410            // 2. prepare to create a large contract
411            0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (exceeds 0x6000)
412            0x60, 0x00, // PUSH1 0x00 (the memory offset)
413            0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
414            0xf0, // CREATE
415            // 3. store the address returned by CREATE to the memory position 0
416            0x60, 0x00, // PUSH1 0x00
417            0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
418            // 4. return the result
419            0x60, 0x20, // PUSH1 0x20 (32 bytes)
420            0x60, 0x00, // PUSH1 0x00
421            0xf3, // RETURN
422        ];
423
424        // deploy factory contract
425        let factory_bytecode: Bytes = factory_code.into();
426        let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
427            .expect("factory contract deployment failed");
428
429        // get factory contract address
430        let factory_address = match &factory_result {
431            ExecutionResult::Success {
432                output: Output::Create(_, Some(addr)),
433                ..
434            } => *addr,
435            _ => panic!("factory contract deployment failed: {factory_result:?}"),
436        };
437
438        // call factory contract to create sub contract
439        let tx_caller = address!("0x0000000000000000000000000000000000100000");
440        let call_result = Context::mainnet()
441            .with_db(CacheDB::<EmptyDB>::default())
442            .build_mainnet()
443            .transact_commit(
444                TxEnv::builder()
445                    .caller(tx_caller)
446                    .kind(TxKind::Call(factory_address))
447                    .data(Bytes::new())
448                    .build()
449                    .unwrap(),
450            )
451            .expect("call factory contract failed");
452
453        match &call_result {
454            ExecutionResult::Success { output, .. } => match output {
455                Output::Call(bytes) => {
456                    if !bytes.is_empty() {
457                        assert!(
458                            bytes.iter().all(|&b| b == 0),
459                            "When CREATE operation failed, it should return all zero address"
460                        );
461                    }
462                }
463                _ => panic!("unexpected output type"),
464            },
465            _ => panic!("execution result is not Success"),
466        }
467    }
468
469    #[test]
470    fn test_eip170_create_opcode_size_limit_success() {
471        // 1. create a "factory" contract, which will use the CREATE opcode to create another contract
472        // 2. the sub contract generated by the factory contract does not exceed the EIP-170 limit, so it should be created successfully
473
474        // the bytecode of the factory contract:
475        // PUSH1 0x01      - the value for MSTORE
476        // PUSH1 0x00      - the memory position
477        // MSTORE          - store a non-zero value at the beginning of memory
478
479        // PUSH3 0x6000    - the return size (0x6000)
480        // PUSH1 0x00      - the memory offset
481        // PUSH1 0x00      - the amount of ETH sent
482        // CREATE          - create contract instruction (create contract from current memory)
483
484        // PUSH1 0x00      - the return value storage position
485        // MSTORE          - store the address returned by CREATE to the memory position 0
486        // PUSH1 0x20      - the return size (32 bytes)
487        // PUSH1 0x00      - the return offset
488        // RETURN          - return the result
489
490        let factory_code = vec![
491            // 1. store a non-zero value at the beginning of memory
492            0x60, 0x01, // PUSH1 0x01
493            0x60, 0x00, // PUSH1 0x00
494            0x52, // MSTORE
495            // 2. prepare to create a contract
496            0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000 (0x6000)
497            0x60, 0x00, // PUSH1 0x00 (the memory offset)
498            0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
499            0xf0, // CREATE
500            // 3. store the address returned by CREATE to the memory position 0
501            0x60, 0x00, // PUSH1 0x00
502            0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
503            // 4. return the result
504            0x60, 0x20, // PUSH1 0x20 (32 bytes)
505            0x60, 0x00, // PUSH1 0x00
506            0xf3, // RETURN
507        ];
508
509        // deploy factory contract
510        let factory_bytecode: Bytes = factory_code.into();
511        let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
512            .expect("factory contract deployment failed");
513        // get factory contract address
514        let factory_address = match &factory_result {
515            ExecutionResult::Success {
516                output: Output::Create(_, Some(addr)),
517                ..
518            } => *addr,
519            _ => panic!("factory contract deployment failed: {factory_result:?}"),
520        };
521
522        // call factory contract to create sub contract
523        let tx_caller = address!("0x0000000000000000000000000000000000100000");
524        let call_result = Context::mainnet()
525            .with_db(CacheDB::<EmptyDB>::default())
526            .build_mainnet()
527            .transact_commit(
528                TxEnv::builder()
529                    .caller(tx_caller)
530                    .kind(TxKind::Call(factory_address))
531                    .data(Bytes::new())
532                    .build()
533                    .unwrap(),
534            )
535            .expect("call factory contract failed");
536
537        match &call_result {
538            ExecutionResult::Success { output, .. } => {
539                match output {
540                    Output::Call(bytes) => {
541                        // check if CREATE operation is successful (return non-zero address)
542                        if !bytes.is_empty() {
543                            assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
544                        }
545                    }
546                    _ => panic!("unexpected output type"),
547                }
548            }
549            _ => panic!("execution result is not Success"),
550        }
551    }
552}