1use context_interface::{
2 result::{InvalidHeader, InvalidTransaction},
3 transaction::{Transaction, TransactionType},
4 Block, Cfg, ContextTr,
5};
6use core::cmp;
7use interpreter::gas::{self, InitialAndFloorGas};
8use primitives::{eip4844, hardfork::SpecId, B256};
9
10pub fn validate_env<CTX: ContextTr, ERROR: From<InvalidHeader> + From<InvalidTransaction>>(
12 context: CTX,
13) -> Result<(), ERROR> {
14 let spec = context.cfg().spec().into();
15 if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
17 return Err(InvalidHeader::PrevrandaoNotSet.into());
18 }
19 if spec.is_enabled_in(SpecId::CANCUN) && context.block().blob_excess_gas_and_price().is_none() {
21 return Err(InvalidHeader::ExcessBlobGasNotSet.into());
22 }
23 validate_tx_env::<CTX>(context, spec).map_err(Into::into)
24}
25
26pub fn validate_priority_fee_tx(
28 max_fee: u128,
29 max_priority_fee: u128,
30 base_fee: Option<u128>,
31 disable_priority_fee_check: bool,
32) -> Result<(), InvalidTransaction> {
33 if !disable_priority_fee_check && max_priority_fee > max_fee {
34 return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
36 }
37
38 if let Some(base_fee) = base_fee {
40 let effective_gas_price = cmp::min(max_fee, base_fee.saturating_add(max_priority_fee));
41 if effective_gas_price < base_fee {
42 return Err(InvalidTransaction::GasPriceLessThanBasefee);
43 }
44 }
45
46 Ok(())
47}
48
49pub fn validate_eip4844_tx(
51 blobs: &[B256],
52 max_blob_fee: u128,
53 block_blob_gas_price: u128,
54 max_blobs: Option<u64>,
55) -> Result<(), InvalidTransaction> {
56 if block_blob_gas_price > max_blob_fee {
58 return Err(InvalidTransaction::BlobGasPriceGreaterThanMax {
59 block_blob_gas_price,
60 tx_max_fee_per_blob_gas: max_blob_fee,
61 });
62 }
63
64 if blobs.is_empty() {
66 return Err(InvalidTransaction::EmptyBlobs);
67 }
68
69 for blob in blobs {
71 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
72 return Err(InvalidTransaction::BlobVersionNotSupported);
73 }
74 }
75
76 if let Some(max_blobs) = max_blobs {
79 if blobs.len() > max_blobs as usize {
80 return Err(InvalidTransaction::TooManyBlobs {
81 have: blobs.len(),
82 max: max_blobs as usize,
83 });
84 }
85 }
86 Ok(())
87}
88
89pub fn validate_tx_env<CTX: ContextTr>(
91 context: CTX,
92 spec_id: SpecId,
93) -> Result<(), InvalidTransaction> {
94 let tx_type = context.tx().tx_type();
96 let tx = context.tx();
97
98 let base_fee = if context.cfg().is_base_fee_check_disabled() {
99 None
100 } else {
101 Some(context.block().basefee() as u128)
102 };
103
104 let tx_type = TransactionType::from(tx_type);
105
106 if context.cfg().tx_chain_id_check() {
109 if let Some(chain_id) = tx.chain_id() {
110 if chain_id != context.cfg().chain_id() {
111 return Err(InvalidTransaction::InvalidChainId);
112 }
113 } else if !tx_type.is_legacy() && !tx_type.is_custom() {
114 return Err(InvalidTransaction::MissingChainId);
116 }
117 }
118
119 let cap = context.cfg().tx_gas_limit_cap();
121 if tx.gas_limit() > cap {
122 return Err(InvalidTransaction::TxGasLimitGreaterThanCap {
123 gas_limit: tx.gas_limit(),
124 cap,
125 });
126 }
127
128 let disable_priority_fee_check = context.cfg().is_priority_fee_check_disabled();
129
130 match tx_type {
131 TransactionType::Legacy => {
132 if let Some(base_fee) = base_fee {
134 if tx.gas_price() < base_fee {
135 return Err(InvalidTransaction::GasPriceLessThanBasefee);
136 }
137 }
138 }
139 TransactionType::Eip2930 => {
140 if !spec_id.is_enabled_in(SpecId::BERLIN) {
142 return Err(InvalidTransaction::Eip2930NotSupported);
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 validate_priority_fee_tx(
157 tx.max_fee_per_gas(),
158 tx.max_priority_fee_per_gas().unwrap_or_default(),
159 base_fee,
160 disable_priority_fee_check,
161 )?;
162 }
163 TransactionType::Eip4844 => {
164 if !spec_id.is_enabled_in(SpecId::CANCUN) {
165 return Err(InvalidTransaction::Eip4844NotSupported);
166 }
167
168 validate_priority_fee_tx(
169 tx.max_fee_per_gas(),
170 tx.max_priority_fee_per_gas().unwrap_or_default(),
171 base_fee,
172 disable_priority_fee_check,
173 )?;
174
175 validate_eip4844_tx(
176 tx.blob_versioned_hashes(),
177 tx.max_fee_per_blob_gas(),
178 context.block().blob_gasprice().unwrap_or_default(),
179 context.cfg().max_blobs_per_tx(),
180 )?;
181 }
182 TransactionType::Eip7702 => {
183 if !spec_id.is_enabled_in(SpecId::PRAGUE) {
185 return Err(InvalidTransaction::Eip7702NotSupported);
186 }
187
188 validate_priority_fee_tx(
189 tx.max_fee_per_gas(),
190 tx.max_priority_fee_per_gas().unwrap_or_default(),
191 base_fee,
192 disable_priority_fee_check,
193 )?;
194
195 let auth_list_len = tx.authorization_list_len();
196 if auth_list_len == 0 {
198 return Err(InvalidTransaction::EmptyAuthorizationList);
199 }
200 }
201 TransactionType::Custom => {
202 }
204 };
205
206 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
208 {
209 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
210 }
211
212 if spec_id.is_enabled_in(SpecId::SHANGHAI)
214 && tx.kind().is_create()
215 && context.tx().input().len() > context.cfg().max_initcode_size()
216 {
217 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
218 }
219
220 Ok(())
221}
222
223pub fn validate_initial_tx_gas(
225 tx: impl Transaction,
226 spec: SpecId,
227) -> Result<InitialAndFloorGas, InvalidTransaction> {
228 let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
229
230 if gas.initial_gas > tx.gas_limit() {
232 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
233 gas_limit: tx.gas_limit(),
234 initial_gas: gas.initial_gas,
235 });
236 }
237
238 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
241 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
242 gas_floor: gas.floor_gas,
243 gas_limit: tx.gas_limit(),
244 });
245 };
246
247 Ok(gas)
248}
249
250#[cfg(test)]
251mod tests {
252 use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
253 use bytecode::opcode;
254 use context::{
255 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
256 Context, TxEnv,
257 };
258 use database::{CacheDB, EmptyDB};
259 use primitives::{address, eip3860, eip7907, hardfork::SpecId, Bytes, TxKind};
260
261 fn deploy_contract(
262 bytecode: Bytes,
263 spec_id: Option<SpecId>,
264 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
265 let ctx = Context::mainnet()
266 .modify_cfg_chained(|c| {
267 if let Some(spec_id) = spec_id {
268 c.spec = spec_id;
269 }
270 })
271 .with_db(CacheDB::<EmptyDB>::default());
272
273 let mut evm = ctx.build_mainnet();
274 evm.transact_commit(
275 TxEnv::builder()
276 .kind(TxKind::Create)
277 .data(bytecode.clone())
278 .build()
279 .unwrap(),
280 )
281 }
282
283 #[test]
284 fn test_eip3860_initcode_size_limit_failure() {
285 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
286 let bytecode: Bytes = large_bytecode.into();
287 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
288 assert!(matches!(
289 result,
290 Err(EVMError::Transaction(
291 InvalidTransaction::CreateInitCodeSizeLimit
292 ))
293 ));
294 }
295
296 #[test]
297 fn test_eip3860_initcode_size_limit_success_prague() {
298 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
299 let bytecode: Bytes = large_bytecode.into();
300 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
301 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
302 }
303
304 #[test]
305 fn test_eip7907_initcode_size_limit_failure_osaka() {
306 let large_bytecode = vec![opcode::STOP; eip7907::MAX_INITCODE_SIZE + 1];
307 let bytecode: Bytes = large_bytecode.into();
308 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
309 assert!(matches!(
310 result,
311 Err(EVMError::Transaction(
312 InvalidTransaction::CreateInitCodeSizeLimit
313 ))
314 ));
315 }
316
317 #[test]
318 fn test_eip7907_code_size_limit_failure() {
319 let init_code = vec![
325 0x62, 0x04, 0x00, 0x01, 0x60, 0x00, 0xf3, ];
329 let bytecode: Bytes = init_code.into();
330 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
331 assert!(
332 matches!(
333 result,
334 Ok(ExecutionResult::Halt {
335 reason: HaltReason::CreateContractSizeLimit,
336 ..
337 },)
338 ),
339 "{result:?}"
340 );
341 }
342
343 #[test]
344 fn test_eip170_code_size_limit_failure() {
345 let init_code = vec![
350 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
354 let bytecode: Bytes = init_code.into();
355 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
356 assert!(
357 matches!(
358 result,
359 Ok(ExecutionResult::Halt {
360 reason: HaltReason::CreateContractSizeLimit,
361 ..
362 },)
363 ),
364 "{result:?}"
365 );
366 }
367
368 #[test]
369 fn test_eip170_code_size_limit_success() {
370 let init_code = vec![
375 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
379 let bytecode: Bytes = init_code.into();
380 let result = deploy_contract(bytecode, None);
381 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
382 }
383
384 #[test]
385 fn test_eip170_create_opcode_size_limit_failure() {
386 let factory_code = vec![
406 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
423
424 let factory_bytecode: Bytes = factory_code.into();
426 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
427 .expect("factory contract deployment failed");
428
429 let factory_address = match &factory_result {
431 ExecutionResult::Success {
432 output: Output::Create(_, Some(addr)),
433 ..
434 } => *addr,
435 _ => panic!("factory contract deployment failed: {factory_result:?}"),
436 };
437
438 let tx_caller = address!("0x0000000000000000000000000000000000100000");
440 let call_result = Context::mainnet()
441 .with_db(CacheDB::<EmptyDB>::default())
442 .build_mainnet()
443 .transact_commit(
444 TxEnv::builder()
445 .caller(tx_caller)
446 .kind(TxKind::Call(factory_address))
447 .data(Bytes::new())
448 .build()
449 .unwrap(),
450 )
451 .expect("call factory contract failed");
452
453 match &call_result {
454 ExecutionResult::Success { output, .. } => match output {
455 Output::Call(bytes) => {
456 if !bytes.is_empty() {
457 assert!(
458 bytes.iter().all(|&b| b == 0),
459 "When CREATE operation failed, it should return all zero address"
460 );
461 }
462 }
463 _ => panic!("unexpected output type"),
464 },
465 _ => panic!("execution result is not Success"),
466 }
467 }
468
469 #[test]
470 fn test_eip170_create_opcode_size_limit_success() {
471 let factory_code = vec![
491 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
508
509 let factory_bytecode: Bytes = factory_code.into();
511 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
512 .expect("factory contract deployment failed");
513 let factory_address = match &factory_result {
515 ExecutionResult::Success {
516 output: Output::Create(_, Some(addr)),
517 ..
518 } => *addr,
519 _ => panic!("factory contract deployment failed: {factory_result:?}"),
520 };
521
522 let tx_caller = address!("0x0000000000000000000000000000000000100000");
524 let call_result = Context::mainnet()
525 .with_db(CacheDB::<EmptyDB>::default())
526 .build_mainnet()
527 .transact_commit(
528 TxEnv::builder()
529 .caller(tx_caller)
530 .kind(TxKind::Call(factory_address))
531 .data(Bytes::new())
532 .build()
533 .unwrap(),
534 )
535 .expect("call factory contract failed");
536
537 match &call_result {
538 ExecutionResult::Success { output, .. } => {
539 match output {
540 Output::Call(bytes) => {
541 if !bytes.is_empty() {
543 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
544 }
545 }
546 _ => panic!("unexpected output type"),
547 }
548 }
549 _ => panic!("execution result is not Success"),
550 }
551 }
552}