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::{eip4844, hardfork::SpecId, B256, U256};
10use state::AccountInfo;
11use std::boxed::Box;
12
13pub fn validate_env<CTX: ContextTr, ERROR: From<InvalidHeader> + From<InvalidTransaction>>(
14 context: CTX,
15) -> Result<(), ERROR> {
16 let spec = context.cfg().spec().into();
17 if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
19 return Err(InvalidHeader::PrevrandaoNotSet.into());
20 }
21 if spec.is_enabled_in(SpecId::CANCUN) && context.block().blob_excess_gas_and_price().is_none() {
23 return Err(InvalidHeader::ExcessBlobGasNotSet.into());
24 }
25 validate_tx_env::<CTX, InvalidTransaction>(context, spec).map_err(Into::into)
26}
27
28pub fn validate_tx_against_state<
29 CTX: ContextTr,
30 ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
31>(
32 mut context: CTX,
33) -> Result<(), ERROR> {
34 let tx_caller = context.tx().caller();
35
36 let account = context.journal().load_account_code(tx_caller)?;
38 let account = account.data.info.clone();
39
40 validate_tx_against_account(&account, context, U256::ZERO)?;
41 Ok(())
42}
43
44pub fn validate_priority_fee_tx(
46 max_fee: u128,
47 max_priority_fee: u128,
48 base_fee: Option<u128>,
49) -> Result<(), InvalidTransaction> {
50 if max_priority_fee > max_fee {
51 return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
53 }
54
55 if let Some(base_fee) = base_fee {
57 let effective_gas_price = cmp::min(max_fee, base_fee.saturating_add(max_priority_fee));
58 if effective_gas_price < base_fee {
59 return Err(InvalidTransaction::GasPriceLessThanBasefee);
60 }
61 }
62
63 Ok(())
64}
65
66pub fn validate_eip4844_tx(
68 blobs: &[B256],
69 max_blob_fee: u128,
70 block_blob_gas_price: u128,
71 max_blobs: u8,
72) -> Result<(), InvalidTransaction> {
73 if block_blob_gas_price > max_blob_fee {
75 return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
76 }
77
78 if blobs.is_empty() {
80 return Err(InvalidTransaction::EmptyBlobs);
81 }
82
83 for blob in blobs {
85 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
86 return Err(InvalidTransaction::BlobVersionNotSupported);
87 }
88 }
89
90 if blobs.len() > max_blobs as usize {
93 return Err(InvalidTransaction::TooManyBlobs {
94 have: blobs.len(),
95 max: max_blobs as usize,
96 });
97 }
98 Ok(())
99}
100
101pub fn validate_tx_env<CTX: ContextTr, Error>(
103 context: CTX,
104 spec_id: SpecId,
105) -> Result<(), InvalidTransaction> {
106 let tx_type = context.tx().tx_type();
108 let tx = context.tx();
109
110 let base_fee = if context.cfg().is_base_fee_check_disabled() {
111 None
112 } else {
113 Some(context.block().basefee() as u128)
114 };
115
116 match TransactionType::from(tx_type) {
117 TransactionType::Legacy => {
118 if let Some(chain_id) = tx.chain_id() {
121 if chain_id != context.cfg().chain_id() {
122 return Err(InvalidTransaction::InvalidChainId);
123 }
124 }
125 if let Some(base_fee) = base_fee {
127 if tx.gas_price() < base_fee {
128 return Err(InvalidTransaction::GasPriceLessThanBasefee);
129 }
130 }
131 }
132 TransactionType::Eip2930 => {
133 if !spec_id.is_enabled_in(SpecId::BERLIN) {
135 return Err(InvalidTransaction::Eip2930NotSupported);
136 }
137
138 if Some(context.cfg().chain_id()) != tx.chain_id() {
139 return Err(InvalidTransaction::InvalidChainId);
140 }
141
142 if let Some(base_fee) = base_fee {
144 if tx.gas_price() < base_fee {
145 return Err(InvalidTransaction::GasPriceLessThanBasefee);
146 }
147 }
148 }
149 TransactionType::Eip1559 => {
150 if !spec_id.is_enabled_in(SpecId::LONDON) {
151 return Err(InvalidTransaction::Eip1559NotSupported);
152 }
153
154 if Some(context.cfg().chain_id()) != tx.chain_id() {
155 return Err(InvalidTransaction::InvalidChainId);
156 }
157
158 validate_priority_fee_tx(
159 tx.max_fee_per_gas(),
160 tx.max_priority_fee_per_gas().unwrap_or_default(),
161 base_fee,
162 )?;
163 }
164 TransactionType::Eip4844 => {
165 if !spec_id.is_enabled_in(SpecId::CANCUN) {
166 return Err(InvalidTransaction::Eip4844NotSupported);
167 }
168
169 if Some(context.cfg().chain_id()) != tx.chain_id() {
170 return Err(InvalidTransaction::InvalidChainId);
171 }
172
173 validate_priority_fee_tx(
174 tx.max_fee_per_gas(),
175 tx.max_priority_fee_per_gas().unwrap_or_default(),
176 base_fee,
177 )?;
178
179 validate_eip4844_tx(
180 tx.blob_versioned_hashes(),
181 tx.max_fee_per_blob_gas(),
182 context.block().blob_gasprice().unwrap_or_default(),
183 context.cfg().blob_max_count(spec_id),
184 )?;
185 }
186 TransactionType::Eip7702 => {
187 if !spec_id.is_enabled_in(SpecId::PRAGUE) {
189 return Err(InvalidTransaction::Eip7702NotSupported);
190 }
191
192 if Some(context.cfg().chain_id()) != tx.chain_id() {
193 return Err(InvalidTransaction::InvalidChainId);
194 }
195
196 validate_priority_fee_tx(
197 tx.max_fee_per_gas(),
198 tx.max_priority_fee_per_gas().unwrap_or_default(),
199 base_fee,
200 )?;
201
202 let auth_list_len = tx.authorization_list_len();
203 if auth_list_len == 0 {
205 return Err(InvalidTransaction::EmptyAuthorizationList);
206 }
207 }
208 TransactionType::Custom => {
209 }
211 };
212
213 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
215 {
216 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
217 }
218
219 if spec_id.is_enabled_in(SpecId::SHANGHAI) && tx.kind().is_create() {
221 let max_initcode_size = context.cfg().max_code_size().saturating_mul(2);
222 if context.tx().input().len() > max_initcode_size {
223 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
224 }
225 }
226
227 Ok(())
228}
229
230#[inline]
232pub fn validate_tx_against_account<CTX: ContextTr>(
233 account: &AccountInfo,
234 context: CTX,
235 additional_cost: U256,
236) -> Result<(), InvalidTransaction> {
237 let tx = context.tx();
238 let tx_type = context.tx().tx_type();
239 if !context.cfg().is_eip3607_disabled() {
243 let bytecode = &account.code.as_ref().unwrap();
244 if !bytecode.is_empty() && !bytecode.is_eip7702() {
247 return Err(InvalidTransaction::RejectCallerWithCode);
248 }
249 }
250
251 if !context.cfg().is_nonce_check_disabled() {
253 let tx = tx.nonce();
254 let state = account.nonce;
255 match tx.cmp(&state) {
256 Ordering::Greater => {
257 return Err(InvalidTransaction::NonceTooHigh { tx, state });
258 }
259 Ordering::Less => {
260 return Err(InvalidTransaction::NonceTooLow { tx, state });
261 }
262 _ => {}
263 }
264 }
265
266 let mut balance_check = U256::from(tx.gas_limit())
268 .checked_mul(U256::from(tx.max_fee_per_gas()))
269 .and_then(|gas_cost| gas_cost.checked_add(tx.value()))
270 .and_then(|gas_cost| gas_cost.checked_add(additional_cost))
271 .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
272
273 if tx_type == TransactionType::Eip4844 {
274 let data_fee = tx.calc_max_data_fee();
275 balance_check = balance_check
276 .checked_add(data_fee)
277 .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
278 }
279
280 if balance_check > account.balance && !context.cfg().is_balance_check_disabled() {
283 return Err(InvalidTransaction::LackOfFundForMaxFee {
284 fee: Box::new(balance_check),
285 balance: Box::new(account.balance),
286 });
287 }
288
289 Ok(())
290}
291
292pub fn validate_initial_tx_gas(
294 tx: impl Transaction,
295 spec: SpecId,
296) -> Result<InitialAndFloorGas, InvalidTransaction> {
297 let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
298
299 if gas.initial_gas > tx.gas_limit() {
301 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
302 gas_limit: tx.gas_limit(),
303 initial_gas: gas.initial_gas,
304 });
305 }
306
307 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
310 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
311 gas_floor: gas.floor_gas,
312 gas_limit: tx.gas_limit(),
313 });
314 };
315
316 Ok(gas)
317}
318
319#[cfg(test)]
320mod tests {
321 use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
322 use bytecode::opcode;
323 use context::{
324 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
325 Context,
326 };
327 use database::{CacheDB, EmptyDB};
328 use primitives::{address, Address, Bytes, TxKind, MAX_INITCODE_SIZE};
329
330 fn deploy_contract(
331 bytecode: Bytes,
332 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
333 let ctx = Context::mainnet()
334 .modify_tx_chained(|tx| {
335 tx.kind = TxKind::Create;
336 tx.data = bytecode.clone();
337 })
338 .with_db(CacheDB::<EmptyDB>::default());
339
340 let mut evm = ctx.build_mainnet();
341 evm.replay_commit()
342 }
343
344 #[test]
345 fn test_eip3860_initcode_size_limit_failure() {
346 let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE + 1];
347 let bytecode: Bytes = large_bytecode.into();
348 let result = deploy_contract(bytecode);
349 assert!(matches!(
350 result,
351 Err(EVMError::Transaction(
352 InvalidTransaction::CreateInitCodeSizeLimit
353 ))
354 ));
355 }
356
357 #[test]
358 fn test_eip3860_initcode_size_limit_success() {
359 let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE];
360 let bytecode: Bytes = large_bytecode.into();
361 let result = deploy_contract(bytecode);
362 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
363 }
364
365 #[test]
366 fn test_eip170_code_size_limit_failure() {
367 let init_code = vec![
372 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
376 let bytecode: Bytes = init_code.into();
377 let result = deploy_contract(bytecode);
378 assert!(matches!(
379 result,
380 Ok(ExecutionResult::Halt {
381 reason: HaltReason::CreateContractSizeLimit,
382 ..
383 },)
384 ));
385 }
386
387 #[test]
388 fn test_eip170_code_size_limit_success() {
389 let init_code = vec![
394 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
398 let bytecode: Bytes = init_code.into();
399 let result = deploy_contract(bytecode);
400 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
401 }
402
403 #[test]
404 fn test_eip170_create_opcode_size_limit_failure() {
405 let factory_code = vec![
425 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
442
443 let factory_bytecode: Bytes = factory_code.into();
445 let factory_result =
446 deploy_contract(factory_bytecode).expect("factory contract deployment failed");
447
448 let factory_address = match &factory_result {
450 ExecutionResult::Success { output, .. } => match output {
451 Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
452 },
453 _ => panic!("factory contract deployment failed"),
454 };
455
456 let tx_caller = address!("0x0000000000000000000000000000000000100000");
458 let call_result = Context::mainnet()
459 .modify_tx_chained(|tx| {
460 tx.caller = tx_caller;
461 tx.kind = TxKind::Call(factory_address);
462 tx.data = Bytes::new();
463 })
464 .with_db(CacheDB::<EmptyDB>::default())
465 .build_mainnet()
466 .replay_commit()
467 .expect("call factory contract failed");
468
469 match &call_result {
470 ExecutionResult::Success { output, .. } => match output {
471 Output::Call(bytes) => {
472 if !bytes.is_empty() {
473 assert!(
474 bytes.iter().all(|&b| b == 0),
475 "When CREATE operation failed, it should return all zero address"
476 );
477 }
478 }
479 _ => panic!("unexpected output type"),
480 },
481 _ => panic!("execution result is not Success"),
482 }
483 }
484
485 #[test]
486 fn test_eip170_create_opcode_size_limit_success() {
487 let factory_code = vec![
507 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
524
525 let factory_bytecode: Bytes = factory_code.into();
527 let factory_result =
528 deploy_contract(factory_bytecode).expect("factory contract deployment failed");
529 let factory_address = match &factory_result {
531 ExecutionResult::Success { output, .. } => match output {
532 Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
533 },
534 _ => panic!("factory contract deployment failed"),
535 };
536
537 let tx_caller = address!("0x0000000000000000000000000000000000100000");
539 let call_result = Context::mainnet()
540 .modify_tx_chained(|tx| {
541 tx.caller = tx_caller;
542 tx.kind = TxKind::Call(factory_address);
543 tx.data = Bytes::new();
544 })
545 .with_db(CacheDB::<EmptyDB>::default())
546 .build_mainnet()
547 .replay_commit()
548 .expect("call factory contract failed");
549
550 match &call_result {
551 ExecutionResult::Success { output, .. } => {
552 match output {
553 Output::Call(bytes) => {
554 if !bytes.is_empty() {
556 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
557 }
558 }
559 _ => panic!("unexpected output type"),
560 }
561 }
562 _ => panic!("execution result is not Success"),
563 }
564 }
565}