revm_handler/
validation.rs1use 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 if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
22 return Err(InvalidHeader::PrevrandaoNotSet.into());
23 }
24 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 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
47pub 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 return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
56 }
57
58 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
69pub 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 if block_blob_gas_price > max_blob_fee {
78 return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
79 }
80
81 if blobs.is_empty() {
83 return Err(InvalidTransaction::EmptyBlobs);
84 }
85
86 for blob in blobs {
88 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
89 return Err(InvalidTransaction::BlobVersionNotSupported);
90 }
91 }
92
93 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
104pub fn validate_tx_env<CTX: ContextTr, Error>(
106 context: CTX,
107 spec_id: SpecId,
108) -> Result<(), InvalidTransaction> {
109 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 if let Some(chain_id) = tx.chain_id() {
124 if chain_id != context.cfg().chain_id() {
125 return Err(InvalidTransaction::InvalidChainId);
126 }
127 }
128 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 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 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 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 if auth_list_len == 0 {
208 return Err(InvalidTransaction::EmptyAuthorizationList);
209 }
210 }
211 TransactionType::Custom => {
212 }
214 };
215
216 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
218 {
219 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
220 }
221
222 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#[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 if !context.cfg().is_eip3607_disabled() {
246 let bytecode = &account.code.as_ref().unwrap();
247 if !bytecode.is_empty() && !bytecode.is_eip7702() {
250 return Err(InvalidTransaction::RejectCallerWithCode);
251 }
252 }
253
254 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 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 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
295pub 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 if gas.initial_gas > tx.gas_limit() {
316 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit);
317 }
318
319 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
322 return Err(InvalidTransaction::GasFloorMoreThanGasLimit);
323 };
324
325 Ok(gas)
326}