revm_handler/
validation.rs

1use context_interface::transaction::AccessListTr;
2use context_interface::ContextTr;
3use context_interface::{
4    journaled_state::Journal,
5    result::{InvalidHeader, InvalidTransaction},
6    transaction::{Transaction, TransactionType},
7    Block, Cfg, Database,
8};
9use core::cmp::{self, Ordering};
10use interpreter::gas::{self, InitialAndFloorGas};
11use primitives::{B256, U256};
12use specification::{eip4844, hardfork::SpecId};
13use state::AccountInfo;
14use std::boxed::Box;
15
16pub fn validate_env<CTX: ContextTr, ERROR: From<InvalidHeader> + From<InvalidTransaction>>(
17    context: CTX,
18) -> Result<(), ERROR> {
19    let spec = context.cfg().spec().into();
20    // `prevrandao` is required for the merge
21    if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
22        return Err(InvalidHeader::PrevrandaoNotSet.into());
23    }
24    // `excess_blob_gas` is required for Cancun
25    if spec.is_enabled_in(SpecId::CANCUN) && context.block().blob_excess_gas_and_price().is_none() {
26        return Err(InvalidHeader::ExcessBlobGasNotSet.into());
27    }
28    validate_tx_env::<CTX, InvalidTransaction>(context, spec).map_err(Into::into)
29}
30
31pub fn validate_tx_against_state<
32    CTX: ContextTr,
33    ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
34>(
35    mut context: CTX,
36) -> Result<(), ERROR> {
37    let tx_caller = context.tx().caller();
38
39    // Load acc
40    let account = context.journal().load_account_code(tx_caller)?;
41    let account = account.data.info.clone();
42
43    validate_tx_against_account(&account, context, U256::ZERO)?;
44    Ok(())
45}
46
47/// Validate transaction that has EIP-1559 priority fee
48pub fn validate_priority_fee_tx(
49    max_fee: u128,
50    max_priority_fee: u128,
51    base_fee: Option<u128>,
52) -> Result<(), InvalidTransaction> {
53    if max_priority_fee > max_fee {
54        // Or gas_max_fee for eip1559
55        return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
56    }
57
58    // Check minimal cost against basefee
59    if let Some(base_fee) = base_fee {
60        let effective_gas_price = cmp::min(max_fee, base_fee.saturating_add(max_priority_fee));
61        if effective_gas_price < base_fee {
62            return Err(InvalidTransaction::GasPriceLessThanBasefee);
63        }
64    }
65
66    Ok(())
67}
68
69/// Validate EIP-4844 transaction.
70pub fn validate_eip4844_tx(
71    blobs: &[B256],
72    max_blob_fee: u128,
73    block_blob_gas_price: u128,
74    max_blobs: u8,
75) -> Result<(), InvalidTransaction> {
76    // Ensure that the user was willing to at least pay the current blob gasprice
77    if block_blob_gas_price > max_blob_fee {
78        return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
79    }
80
81    // There must be at least one blob
82    if blobs.is_empty() {
83        return Err(InvalidTransaction::EmptyBlobs);
84    }
85
86    // All versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG
87    for blob in blobs {
88        if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
89            return Err(InvalidTransaction::BlobVersionNotSupported);
90        }
91    }
92
93    // Ensure the total blob gas spent is at most equal to the limit
94    // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
95    if blobs.len() > max_blobs as usize {
96        return Err(InvalidTransaction::TooManyBlobs {
97            have: blobs.len(),
98            max: max_blobs as usize,
99        });
100    }
101    Ok(())
102}
103
104/// Validate transaction against block and configuration for mainnet.
105pub fn validate_tx_env<CTX: ContextTr, Error>(
106    context: CTX,
107    spec_id: SpecId,
108) -> Result<(), InvalidTransaction> {
109    // Check if the transaction's chain id is correct
110    let tx_type = context.tx().tx_type();
111    let tx = context.tx();
112
113    let base_fee = if context.cfg().is_base_fee_check_disabled() {
114        None
115    } else {
116        Some(context.block().basefee() as u128)
117    };
118
119    match TransactionType::from(tx_type) {
120        TransactionType::Legacy => {
121            // Check chain_id only if it is present in the legacy transaction.
122            // EIP-155: Simple replay attack protection
123            if let Some(chain_id) = tx.chain_id() {
124                if chain_id != context.cfg().chain_id() {
125                    return Err(InvalidTransaction::InvalidChainId);
126                }
127            }
128            // Gas price must be at least the basefee.
129            if let Some(base_fee) = base_fee {
130                if tx.gas_price() < base_fee {
131                    return Err(InvalidTransaction::GasPriceLessThanBasefee);
132                }
133            }
134        }
135        TransactionType::Eip2930 => {
136            // Enabled in BERLIN hardfork
137            if !spec_id.is_enabled_in(SpecId::BERLIN) {
138                return Err(InvalidTransaction::Eip2930NotSupported);
139            }
140
141            if Some(context.cfg().chain_id()) != tx.chain_id() {
142                return Err(InvalidTransaction::InvalidChainId);
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
157            if Some(context.cfg().chain_id()) != tx.chain_id() {
158                return Err(InvalidTransaction::InvalidChainId);
159            }
160
161            validate_priority_fee_tx(
162                tx.max_fee_per_gas(),
163                tx.max_priority_fee_per_gas().unwrap_or_default(),
164                base_fee,
165            )?;
166        }
167        TransactionType::Eip4844 => {
168            if !spec_id.is_enabled_in(SpecId::CANCUN) {
169                return Err(InvalidTransaction::Eip4844NotSupported);
170            }
171
172            if Some(context.cfg().chain_id()) != tx.chain_id() {
173                return Err(InvalidTransaction::InvalidChainId);
174            }
175
176            validate_priority_fee_tx(
177                tx.max_fee_per_gas(),
178                tx.max_priority_fee_per_gas().unwrap_or_default(),
179                base_fee,
180            )?;
181
182            validate_eip4844_tx(
183                tx.blob_versioned_hashes(),
184                tx.max_fee_per_blob_gas(),
185                context.block().blob_gasprice().unwrap_or_default(),
186                context.cfg().blob_max_count(spec_id),
187            )?;
188        }
189        TransactionType::Eip7702 => {
190            // Check if EIP-7702 transaction is enabled.
191            if !spec_id.is_enabled_in(SpecId::PRAGUE) {
192                return Err(InvalidTransaction::Eip7702NotSupported);
193            }
194
195            if Some(context.cfg().chain_id()) != tx.chain_id() {
196                return Err(InvalidTransaction::InvalidChainId);
197            }
198
199            validate_priority_fee_tx(
200                tx.max_fee_per_gas(),
201                tx.max_priority_fee_per_gas().unwrap_or_default(),
202                base_fee,
203            )?;
204
205            let auth_list_len = tx.authorization_list_len();
206            // The transaction is considered invalid if the length of authorization_list is zero.
207            if auth_list_len == 0 {
208                return Err(InvalidTransaction::EmptyAuthorizationList);
209            }
210        }
211        TransactionType::Custom => {
212            // Custom transaction type check is not done here.
213        }
214    };
215
216    // Check if gas_limit is more than block_gas_limit
217    if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
218    {
219        return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
220    }
221
222    // EIP-3860: Limit and meter initcode
223    if spec_id.is_enabled_in(SpecId::SHANGHAI) && tx.kind().is_create() {
224        let max_initcode_size = context.cfg().max_code_size().saturating_mul(2);
225        if context.tx().input().len() > max_initcode_size {
226            return Err(InvalidTransaction::CreateInitCodeSizeLimit);
227        }
228    }
229
230    Ok(())
231}
232
233/// Validate account against the transaction.
234#[inline]
235pub fn validate_tx_against_account<CTX: ContextTr>(
236    account: &AccountInfo,
237    context: CTX,
238    additional_cost: U256,
239) -> Result<(), InvalidTransaction> {
240    let tx = context.tx();
241    let tx_type = context.tx().tx_type();
242    // EIP-3607: Reject transactions from senders with deployed code
243    // This EIP is introduced after london but there was no collision in past
244    // so we can leave it enabled always
245    if !context.cfg().is_eip3607_disabled() {
246        let bytecode = &account.code.as_ref().unwrap();
247        // Allow EOAs whose code is a valid delegation designation,
248        // i.e. 0xef0100 || address, to continue to originate transactions.
249        if !bytecode.is_empty() && !bytecode.is_eip7702() {
250            return Err(InvalidTransaction::RejectCallerWithCode);
251        }
252    }
253
254    // Check that the transaction's nonce is correct
255    if !context.cfg().is_nonce_check_disabled() {
256        let tx = tx.nonce();
257        let state = account.nonce;
258        match tx.cmp(&state) {
259            Ordering::Greater => {
260                return Err(InvalidTransaction::NonceTooHigh { tx, state });
261            }
262            Ordering::Less => {
263                return Err(InvalidTransaction::NonceTooLow { tx, state });
264            }
265            _ => {}
266        }
267    }
268
269    // gas_limit * max_fee + value + additional_gas_cost
270    let mut balance_check = U256::from(tx.gas_limit())
271        .checked_mul(U256::from(tx.max_fee_per_gas()))
272        .and_then(|gas_cost| gas_cost.checked_add(tx.value()))
273        .and_then(|gas_cost| gas_cost.checked_add(additional_cost))
274        .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
275
276    if tx_type == TransactionType::Eip4844 {
277        let data_fee = tx.calc_max_data_fee();
278        balance_check = balance_check
279            .checked_add(data_fee)
280            .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
281    }
282
283    // Check if account has enough balance for `gas_limit * max_fee`` and value transfer.
284    // Transfer will be done inside `*_inner` functions.
285    if balance_check > account.balance && !context.cfg().is_balance_check_disabled() {
286        return Err(InvalidTransaction::LackOfFundForMaxFee {
287            fee: Box::new(balance_check),
288            balance: Box::new(account.balance),
289        });
290    }
291
292    Ok(())
293}
294
295/// Validate initial transaction gas.
296pub fn validate_initial_tx_gas(
297    tx: impl Transaction,
298    spec: SpecId,
299) -> Result<InitialAndFloorGas, InvalidTransaction> {
300    let (accounts, storages) = tx
301        .access_list()
302        .map(|al| al.access_list_nums())
303        .unwrap_or_default();
304
305    let gas = gas::calculate_initial_tx_gas(
306        spec,
307        tx.input(),
308        tx.kind().is_create(),
309        accounts as u64,
310        storages as u64,
311        tx.authorization_list_len() as u64,
312    );
313
314    // Additional check to see if limit is big enough to cover initial gas.
315    if gas.initial_gas > tx.gas_limit() {
316        return Err(InvalidTransaction::CallGasCostMoreThanGasLimit);
317    }
318
319    // EIP-7623: Increase calldata cost
320    // floor gas should be less than gas limit.
321    if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
322        return Err(InvalidTransaction::GasFloorMoreThanGasLimit);
323    };
324
325    Ok(gas)
326}