revm_handler/
validation.rs

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