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