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