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 if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
21 return Err(InvalidHeader::PrevrandaoNotSet.into());
22 }
23 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 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
46pub 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 return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
55 }
56
57 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
68pub 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 if block_blob_gas_price > max_blob_fee {
77 return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
78 }
79
80 if blobs.is_empty() {
82 return Err(InvalidTransaction::EmptyBlobs);
83 }
84
85 for blob in blobs {
87 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
88 return Err(InvalidTransaction::BlobVersionNotSupported);
89 }
90 }
91
92 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
103pub fn validate_tx_env<CTX: ContextTr, Error>(
105 context: CTX,
106 spec_id: SpecId,
107) -> Result<(), InvalidTransaction> {
108 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 if let Some(chain_id) = tx.chain_id() {
123 if chain_id != context.cfg().chain_id() {
124 return Err(InvalidTransaction::InvalidChainId);
125 }
126 }
127 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 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 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 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 if auth_list_len == 0 {
207 return Err(InvalidTransaction::EmptyAuthorizationList);
208 }
209 }
210 TransactionType::Eip7873 => {
211 if !spec_id.is_enabled_in(SpecId::OSAKA) {
213 return Err(InvalidTransaction::Eip7873NotSupported);
214 }
215
216 if Some(context.cfg().chain_id()) != tx.chain_id() {
218 return Err(InvalidTransaction::InvalidChainId);
219 }
220
221 validate_eip7873_initcodes(tx.initcodes())?;
223
224 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 }
237 TransactionType::Custom => {
238 }
240 };
241
242 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
244 {
245 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
246 }
247
248 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#[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 if !context.cfg().is_eip3607_disabled() {
272 let bytecode = &account.code.as_ref().unwrap();
273 if !bytecode.is_empty() && !bytecode.is_eip7702() {
276 return Err(InvalidTransaction::RejectCallerWithCode);
277 }
278 }
279
280 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 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 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
321pub fn validate_eip7873_initcodes(initcodes: &[Bytes]) -> Result<(), InvalidTransaction> {
326 let mut i = 0;
327 for initcode in initcodes {
328 if initcode.is_empty() {
330 return Err(InvalidTransaction::Eip7873EmptyInitcode { i });
331 }
332
333 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 if i == 0 {
346 return Err(InvalidTransaction::Eip7873EmptyInitcodeList);
347 }
348
349 if i > MAX_INITCODE_COUNT {
351 return Err(InvalidTransaction::Eip7873TooManyInitcodes { size: i });
352 }
353
354 Ok(())
355}
356
357pub 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 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 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 let init_code = vec![
437 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
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 let init_code = vec![
459 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
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 let factory_code = vec![
490 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
507
508 let factory_bytecode: Bytes = factory_code.into();
510 let factory_result =
511 deploy_contract(factory_bytecode).expect("factory contract deployment failed");
512
513 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 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 let factory_code = vec![
572 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
589
590 let factory_bytecode: Bytes = factory_code.into();
592 let factory_result =
593 deploy_contract(factory_bytecode).expect("factory contract deployment failed");
594 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 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 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}