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    let tx_type = TransactionType::from(tx_type);
100
101    // Check chain_id if config is enabled.
102    // EIP-155: Simple replay attack protection
103    if context.cfg().tx_chain_id_check() {
104        if let Some(chain_id) = tx.chain_id() {
105            if chain_id != context.cfg().chain_id() {
106                return Err(InvalidTransaction::InvalidChainId);
107            }
108        } else if !tx_type.is_legacy() && !tx_type.is_custom() {
109            // Legacy transaction are the only one that can omit chain_id.
110            return Err(InvalidTransaction::MissingChainId);
111        }
112    }
113
114    match tx_type {
115        TransactionType::Legacy => {
116            // Gas price must be at least the basefee.
117            if let Some(base_fee) = base_fee {
118                if tx.gas_price() < base_fee {
119                    return Err(InvalidTransaction::GasPriceLessThanBasefee);
120                }
121            }
122        }
123        TransactionType::Eip2930 => {
124            // Enabled in BERLIN hardfork
125            if !spec_id.is_enabled_in(SpecId::BERLIN) {
126                return Err(InvalidTransaction::Eip2930NotSupported);
127            }
128
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::Eip1559 => {
137            if !spec_id.is_enabled_in(SpecId::LONDON) {
138                return Err(InvalidTransaction::Eip1559NotSupported);
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            validate_priority_fee_tx(
153                tx.max_fee_per_gas(),
154                tx.max_priority_fee_per_gas().unwrap_or_default(),
155                base_fee,
156            )?;
157
158            validate_eip4844_tx(
159                tx.blob_versioned_hashes(),
160                tx.max_fee_per_blob_gas(),
161                context.block().blob_gasprice().unwrap_or_default(),
162                context.cfg().blob_max_count(),
163            )?;
164        }
165        TransactionType::Eip7702 => {
166            // Check if EIP-7702 transaction is enabled.
167            if !spec_id.is_enabled_in(SpecId::PRAGUE) {
168                return Err(InvalidTransaction::Eip7702NotSupported);
169            }
170
171            validate_priority_fee_tx(
172                tx.max_fee_per_gas(),
173                tx.max_priority_fee_per_gas().unwrap_or_default(),
174                base_fee,
175            )?;
176
177            let auth_list_len = tx.authorization_list_len();
178            // The transaction is considered invalid if the length of authorization_list is zero.
179            if auth_list_len == 0 {
180                return Err(InvalidTransaction::EmptyAuthorizationList);
181            }
182        }
183        /* // TODO(EOF) EOF removed from spec.
184        TransactionType::Eip7873 => {
185            // Check if EIP-7873 transaction is enabled.
186            if !spec_id.is_enabled_in(SpecId::OSAKA) {
187            return Err(InvalidTransaction::Eip7873NotSupported);
188            }
189            // validate chain id
190            if Some(context.cfg().chain_id()) != tx.chain_id() {
191                return Err(InvalidTransaction::InvalidChainId);
192            }
193
194            // validate initcodes.
195            validate_eip7873_initcodes(tx.initcodes())?;
196
197            // InitcodeTransaction is invalid if the to is nil.
198            if tx.kind().is_create() {
199                return Err(InvalidTransaction::Eip7873MissingTarget);
200            }
201
202            validate_priority_fee_tx(
203                tx.max_fee_per_gas(),
204                tx.max_priority_fee_per_gas().unwrap_or_default(),
205                base_fee,
206            )?;
207        }
208        */
209        TransactionType::Custom => {
210            // Custom transaction type check is not done here.
211        }
212    };
213
214    // Check if gas_limit is more than block_gas_limit
215    if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
216    {
217        return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
218    }
219
220    // EIP-3860: Limit and meter initcode
221    if spec_id.is_enabled_in(SpecId::SHANGHAI) && tx.kind().is_create() {
222        let max_initcode_size = context.cfg().max_code_size().saturating_mul(2);
223        if context.tx().input().len() > max_initcode_size {
224            return Err(InvalidTransaction::CreateInitCodeSizeLimit);
225        }
226    }
227
228    Ok(())
229}
230
231/* TODO(EOF)
232/// Validate Initcode Transaction initcode list, return error if any of the following conditions are met:
233/// * there are zero entries in initcodes, or if there are more than MAX_INITCODE_COUNT entries.
234/// * any entry in initcodes is zero length, or if any entry exceeds MAX_INITCODE_SIZE.
235/// * the to is nil.
236pub fn validate_eip7873_initcodes(initcodes: &[Bytes]) -> Result<(), InvalidTransaction> {
237    let mut i = 0;
238    for initcode in initcodes {
239        // InitcodeTransaction is invalid if any entry in initcodes is zero length
240        if initcode.is_empty() {
241            return Err(InvalidTransaction::Eip7873EmptyInitcode { i });
242        }
243
244        // or if any entry exceeds MAX_INITCODE_SIZE.
245        if initcode.len() > MAX_INITCODE_SIZE {
246            return Err(InvalidTransaction::Eip7873InitcodeTooLarge {
247                i,
248                size: initcode.len(),
249            });
250        }
251
252        i += 1;
253    }
254
255    // InitcodeTransaction is invalid if there are zero entries in initcodes,
256    if i == 0 {
257        return Err(InvalidTransaction::Eip7873EmptyInitcodeList);
258    }
259
260    // or if there are more than MAX_INITCODE_COUNT entries.
261    if i > MAX_INITCODE_COUNT {
262        return Err(InvalidTransaction::Eip7873TooManyInitcodes { size: i });
263    }
264
265    Ok(())
266}
267*/
268
269/// Validate initial transaction gas.
270pub fn validate_initial_tx_gas(
271    tx: impl Transaction,
272    spec: SpecId,
273) -> Result<InitialAndFloorGas, InvalidTransaction> {
274    let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
275
276    // Additional check to see if limit is big enough to cover initial gas.
277    if gas.initial_gas > tx.gas_limit() {
278        return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
279            gas_limit: tx.gas_limit(),
280            initial_gas: gas.initial_gas,
281        });
282    }
283
284    // EIP-7623: Increase calldata cost
285    // floor gas should be less than gas limit.
286    if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
287        return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
288            gas_floor: gas.floor_gas,
289            gas_limit: tx.gas_limit(),
290        });
291    };
292
293    Ok(gas)
294}
295
296#[cfg(test)]
297mod tests {
298    use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
299    use bytecode::opcode;
300    use context::{
301        result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
302        Context, TxEnv,
303    };
304    use database::{CacheDB, EmptyDB};
305    use primitives::{address, Address, Bytes, TxKind, MAX_INITCODE_SIZE};
306
307    fn deploy_contract(
308        bytecode: Bytes,
309    ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
310        let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
311
312        let mut evm = ctx.build_mainnet();
313        evm.transact_commit(TxEnv {
314            kind: TxKind::Create,
315            data: bytecode.clone(),
316            ..Default::default()
317        })
318    }
319
320    #[test]
321    fn test_eip3860_initcode_size_limit_failure() {
322        let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE + 1];
323        let bytecode: Bytes = large_bytecode.into();
324        let result = deploy_contract(bytecode);
325        assert!(matches!(
326            result,
327            Err(EVMError::Transaction(
328                InvalidTransaction::CreateInitCodeSizeLimit
329            ))
330        ));
331    }
332
333    #[test]
334    fn test_eip3860_initcode_size_limit_success() {
335        let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE];
336        let bytecode: Bytes = large_bytecode.into();
337        let result = deploy_contract(bytecode);
338        assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
339    }
340
341    #[test]
342    fn test_eip170_code_size_limit_failure() {
343        // use the simplest method to return a contract code size greater than 0x6000
344        // PUSH3 0x6001 (greater than 0x6000) - return size
345        // PUSH1 0x00 - memory position 0
346        // RETURN - return uninitialized memory, will be filled with 0
347        let init_code = vec![
348            0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (greater than 0x6000)
349            0x60, 0x00, // PUSH1 0
350            0xf3, // RETURN
351        ];
352        let bytecode: Bytes = init_code.into();
353        let result = deploy_contract(bytecode);
354        assert!(matches!(
355            result,
356            Ok(ExecutionResult::Halt {
357                reason: HaltReason::CreateContractSizeLimit,
358                ..
359            },)
360        ));
361    }
362
363    #[test]
364    fn test_eip170_code_size_limit_success() {
365        // use the  simplest method to return a contract code size equal to 0x6000
366        // PUSH3 0x6000 - return size
367        // PUSH1 0x00 - memory position 0
368        // RETURN - return uninitialized memory, will be filled with 0
369        let init_code = vec![
370            0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000
371            0x60, 0x00, // PUSH1 0
372            0xf3, // RETURN
373        ];
374        let bytecode: Bytes = init_code.into();
375        let result = deploy_contract(bytecode);
376        assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
377    }
378
379    #[test]
380    fn test_eip170_create_opcode_size_limit_failure() {
381        // 1. create a "factory" contract, which will use the CREATE opcode to create another large contract
382        // 2. because the sub contract exceeds the EIP-170 limit, the CREATE operation should fail
383
384        // the bytecode of the factory contract:
385        // PUSH1 0x01      - the value for MSTORE
386        // PUSH1 0x00      - the memory position
387        // MSTORE          - store a non-zero value at the beginning of memory
388
389        // PUSH3 0x6001    - the return size (exceeds 0x6000)
390        // PUSH1 0x00      - the memory offset
391        // PUSH1 0x00      - the amount of ETH sent
392        // CREATE          - create contract instruction (create contract from current memory)
393
394        // PUSH1 0x00      - the return value storage position
395        // MSTORE          - store the address returned by CREATE to the memory position 0
396        // PUSH1 0x20      - the return size (32 bytes)
397        // PUSH1 0x00      - the return offset
398        // RETURN          - return the result
399
400        let factory_code = vec![
401            // 1. store a non-zero value at the beginning of memory
402            0x60, 0x01, // PUSH1 0x01
403            0x60, 0x00, // PUSH1 0x00
404            0x52, // MSTORE
405            // 2. prepare to create a large contract
406            0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (exceeds 0x6000)
407            0x60, 0x00, // PUSH1 0x00 (the memory offset)
408            0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
409            0xf0, // CREATE
410            // 3. store the address returned by CREATE to the memory position 0
411            0x60, 0x00, // PUSH1 0x00
412            0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
413            // 4. return the result
414            0x60, 0x20, // PUSH1 0x20 (32 bytes)
415            0x60, 0x00, // PUSH1 0x00
416            0xf3, // RETURN
417        ];
418
419        // deploy factory contract
420        let factory_bytecode: Bytes = factory_code.into();
421        let factory_result =
422            deploy_contract(factory_bytecode).expect("factory contract deployment failed");
423
424        // get factory contract address
425        let factory_address = match &factory_result {
426            ExecutionResult::Success { output, .. } => match output {
427                Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
428            },
429            _ => panic!("factory contract deployment failed"),
430        };
431
432        // call factory contract to create sub contract
433        let tx_caller = address!("0x0000000000000000000000000000000000100000");
434        let call_result = Context::mainnet()
435            .with_db(CacheDB::<EmptyDB>::default())
436            .build_mainnet()
437            .transact_commit(TxEnv {
438                caller: tx_caller,
439                kind: TxKind::Call(factory_address),
440                data: Bytes::new(),
441                ..Default::default()
442            })
443            .expect("call factory contract failed");
444
445        match &call_result {
446            ExecutionResult::Success { output, .. } => match output {
447                Output::Call(bytes) => {
448                    if !bytes.is_empty() {
449                        assert!(
450                            bytes.iter().all(|&b| b == 0),
451                            "When CREATE operation failed, it should return all zero address"
452                        );
453                    }
454                }
455                _ => panic!("unexpected output type"),
456            },
457            _ => panic!("execution result is not Success"),
458        }
459    }
460
461    #[test]
462    fn test_eip170_create_opcode_size_limit_success() {
463        // 1. create a "factory" contract, which will use the CREATE opcode to create another contract
464        // 2. the sub contract generated by the factory contract does not exceed the EIP-170 limit, so it should be created successfully
465
466        // the bytecode of the factory contract:
467        // PUSH1 0x01      - the value for MSTORE
468        // PUSH1 0x00      - the memory position
469        // MSTORE          - store a non-zero value at the beginning of memory
470
471        // PUSH3 0x6000    - the return size (0x6000)
472        // PUSH1 0x00      - the memory offset
473        // PUSH1 0x00      - the amount of ETH sent
474        // CREATE          - create contract instruction (create contract from current memory)
475
476        // PUSH1 0x00      - the return value storage position
477        // MSTORE          - store the address returned by CREATE to the memory position 0
478        // PUSH1 0x20      - the return size (32 bytes)
479        // PUSH1 0x00      - the return offset
480        // RETURN          - return the result
481
482        let factory_code = vec![
483            // 1. store a non-zero value at the beginning of memory
484            0x60, 0x01, // PUSH1 0x01
485            0x60, 0x00, // PUSH1 0x00
486            0x52, // MSTORE
487            // 2. prepare to create a contract
488            0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000 (0x6000)
489            0x60, 0x00, // PUSH1 0x00 (the memory offset)
490            0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
491            0xf0, // CREATE
492            // 3. store the address returned by CREATE to the memory position 0
493            0x60, 0x00, // PUSH1 0x00
494            0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
495            // 4. return the result
496            0x60, 0x20, // PUSH1 0x20 (32 bytes)
497            0x60, 0x00, // PUSH1 0x00
498            0xf3, // RETURN
499        ];
500
501        // deploy factory contract
502        let factory_bytecode: Bytes = factory_code.into();
503        let factory_result =
504            deploy_contract(factory_bytecode).expect("factory contract deployment failed");
505        // get factory contract address
506        let factory_address = match &factory_result {
507            ExecutionResult::Success { output, .. } => match output {
508                Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
509            },
510            _ => panic!("factory contract deployment failed"),
511        };
512
513        // call factory contract to create sub contract
514        let tx_caller = address!("0x0000000000000000000000000000000000100000");
515        let call_result = Context::mainnet()
516            .with_db(CacheDB::<EmptyDB>::default())
517            .build_mainnet()
518            .transact_commit(TxEnv {
519                caller: tx_caller,
520                kind: TxKind::Call(factory_address),
521                data: Bytes::new(),
522                ..Default::default()
523            })
524            .expect("call factory contract failed");
525
526        match &call_result {
527            ExecutionResult::Success { output, .. } => {
528                match output {
529                    Output::Call(bytes) => {
530                        // check if CREATE operation is successful (return non-zero address)
531                        if !bytes.is_empty() {
532                            assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
533                        }
534                    }
535                    _ => panic!("unexpected output type"),
536                }
537            }
538            _ => panic!("execution result is not Success"),
539        }
540    }
541}