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, InvalidTransaction>(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 }
60
61 if blobs.is_empty() {
63 return Err(InvalidTransaction::EmptyBlobs);
64 }
65
66 for blob in blobs {
68 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
69 return Err(InvalidTransaction::BlobVersionNotSupported);
70 }
71 }
72
73 if let Some(max_blobs) = max_blobs {
76 if blobs.len() > max_blobs as usize {
77 return Err(InvalidTransaction::TooManyBlobs {
78 have: blobs.len(),
79 max: max_blobs as usize,
80 });
81 }
82 }
83 Ok(())
84}
85
86pub fn validate_tx_env<CTX: ContextTr, Error>(
88 context: CTX,
89 spec_id: SpecId,
90) -> Result<(), InvalidTransaction> {
91 let tx_type = context.tx().tx_type();
93 let tx = context.tx();
94
95 let base_fee = if context.cfg().is_base_fee_check_disabled() {
96 None
97 } else {
98 Some(context.block().basefee() as u128)
99 };
100
101 let tx_type = TransactionType::from(tx_type);
102
103 if context.cfg().tx_chain_id_check() {
106 if let Some(chain_id) = tx.chain_id() {
107 if chain_id != context.cfg().chain_id() {
108 return Err(InvalidTransaction::InvalidChainId);
109 }
110 } else if !tx_type.is_legacy() && !tx_type.is_custom() {
111 return Err(InvalidTransaction::MissingChainId);
113 }
114 }
115
116 let cap = context.cfg().tx_gas_limit_cap();
118 if tx.gas_limit() > cap {
119 return Err(InvalidTransaction::TxGasLimitGreaterThanCap {
120 gas_limit: tx.gas_limit(),
121 cap,
122 });
123 }
124
125 let disable_priority_fee_check = context.cfg().is_priority_fee_check_disabled();
126
127 match tx_type {
128 TransactionType::Legacy => {
129 if let Some(base_fee) = base_fee {
131 if tx.gas_price() < base_fee {
132 return Err(InvalidTransaction::GasPriceLessThanBasefee);
133 }
134 }
135 }
136 TransactionType::Eip2930 => {
137 if !spec_id.is_enabled_in(SpecId::BERLIN) {
139 return Err(InvalidTransaction::Eip2930NotSupported);
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 validate_priority_fee_tx(
154 tx.max_fee_per_gas(),
155 tx.max_priority_fee_per_gas().unwrap_or_default(),
156 base_fee,
157 disable_priority_fee_check,
158 )?;
159 }
160 TransactionType::Eip4844 => {
161 if !spec_id.is_enabled_in(SpecId::CANCUN) {
162 return Err(InvalidTransaction::Eip4844NotSupported);
163 }
164
165 validate_priority_fee_tx(
166 tx.max_fee_per_gas(),
167 tx.max_priority_fee_per_gas().unwrap_or_default(),
168 base_fee,
169 disable_priority_fee_check,
170 )?;
171
172 validate_eip4844_tx(
173 tx.blob_versioned_hashes(),
174 tx.max_fee_per_blob_gas(),
175 context.block().blob_gasprice().unwrap_or_default(),
176 context.cfg().max_blobs_per_tx(),
177 )?;
178 }
179 TransactionType::Eip7702 => {
180 if !spec_id.is_enabled_in(SpecId::PRAGUE) {
182 return Err(InvalidTransaction::Eip7702NotSupported);
183 }
184
185 validate_priority_fee_tx(
186 tx.max_fee_per_gas(),
187 tx.max_priority_fee_per_gas().unwrap_or_default(),
188 base_fee,
189 disable_priority_fee_check,
190 )?;
191
192 let auth_list_len = tx.authorization_list_len();
193 if auth_list_len == 0 {
195 return Err(InvalidTransaction::EmptyAuthorizationList);
196 }
197 }
198 TransactionType::Custom => {
199 }
201 };
202
203 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
205 {
206 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
207 }
208
209 if spec_id.is_enabled_in(SpecId::SHANGHAI)
211 && tx.kind().is_create()
212 && context.tx().input().len() > context.cfg().max_initcode_size()
213 {
214 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
215 }
216
217 Ok(())
218}
219
220pub fn validate_initial_tx_gas(
222 tx: impl Transaction,
223 spec: SpecId,
224) -> Result<InitialAndFloorGas, InvalidTransaction> {
225 let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
226
227 if gas.initial_gas > tx.gas_limit() {
229 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
230 gas_limit: tx.gas_limit(),
231 initial_gas: gas.initial_gas,
232 });
233 }
234
235 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
238 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
239 gas_floor: gas.floor_gas,
240 gas_limit: tx.gas_limit(),
241 });
242 };
243
244 Ok(gas)
245}
246
247#[cfg(test)]
248mod tests {
249 use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
250 use bytecode::opcode;
251 use context::{
252 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
253 Context, TxEnv,
254 };
255 use database::{CacheDB, EmptyDB};
256 use primitives::{address, eip3860, eip7907, hardfork::SpecId, Bytes, TxKind};
257
258 fn deploy_contract(
259 bytecode: Bytes,
260 spec_id: Option<SpecId>,
261 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
262 let ctx = Context::mainnet()
263 .modify_cfg_chained(|c| {
264 if let Some(spec_id) = spec_id {
265 c.spec = spec_id;
266 }
267 })
268 .with_db(CacheDB::<EmptyDB>::default());
269
270 let mut evm = ctx.build_mainnet();
271 evm.transact_commit(
272 TxEnv::builder()
273 .kind(TxKind::Create)
274 .data(bytecode.clone())
275 .build()
276 .unwrap(),
277 )
278 }
279
280 #[test]
281 fn test_eip3860_initcode_size_limit_failure() {
282 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
283 let bytecode: Bytes = large_bytecode.into();
284 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
285 assert!(matches!(
286 result,
287 Err(EVMError::Transaction(
288 InvalidTransaction::CreateInitCodeSizeLimit
289 ))
290 ));
291 }
292
293 #[test]
294 fn test_eip3860_initcode_size_limit_success_prague() {
295 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
296 let bytecode: Bytes = large_bytecode.into();
297 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
298 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
299 }
300
301 #[test]
302 fn test_eip7907_initcode_size_limit_failure_osaka() {
303 let large_bytecode = vec![opcode::STOP; eip7907::MAX_INITCODE_SIZE + 1];
304 let bytecode: Bytes = large_bytecode.into();
305 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
306 assert!(matches!(
307 result,
308 Err(EVMError::Transaction(
309 InvalidTransaction::CreateInitCodeSizeLimit
310 ))
311 ));
312 }
313
314 #[test]
315 fn test_eip7907_code_size_limit_failure() {
316 let init_code = vec![
322 0x62, 0x04, 0x00, 0x01, 0x60, 0x00, 0xf3, ];
326 let bytecode: Bytes = init_code.into();
327 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
328 assert!(
329 matches!(
330 result,
331 Ok(ExecutionResult::Halt {
332 reason: HaltReason::CreateContractSizeLimit,
333 ..
334 },)
335 ),
336 "{result:?}"
337 );
338 }
339
340 #[test]
341 fn test_eip170_code_size_limit_failure() {
342 let init_code = vec![
347 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
351 let bytecode: Bytes = init_code.into();
352 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
353 assert!(
354 matches!(
355 result,
356 Ok(ExecutionResult::Halt {
357 reason: HaltReason::CreateContractSizeLimit,
358 ..
359 },)
360 ),
361 "{result:?}"
362 );
363 }
364
365 #[test]
366 fn test_eip170_code_size_limit_success() {
367 let init_code = vec![
372 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
376 let bytecode: Bytes = init_code.into();
377 let result = deploy_contract(bytecode, None);
378 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
379 }
380
381 #[test]
382 fn test_eip170_create_opcode_size_limit_failure() {
383 let factory_code = vec![
403 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
420
421 let factory_bytecode: Bytes = factory_code.into();
423 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
424 .expect("factory contract deployment failed");
425
426 let factory_address = match &factory_result {
428 ExecutionResult::Success {
429 output: Output::Create(_, Some(addr)),
430 ..
431 } => *addr,
432 _ => panic!("factory contract deployment failed: {factory_result:?}"),
433 };
434
435 let tx_caller = address!("0x0000000000000000000000000000000000100000");
437 let call_result = Context::mainnet()
438 .with_db(CacheDB::<EmptyDB>::default())
439 .build_mainnet()
440 .transact_commit(
441 TxEnv::builder()
442 .caller(tx_caller)
443 .kind(TxKind::Call(factory_address))
444 .data(Bytes::new())
445 .build()
446 .unwrap(),
447 )
448 .expect("call factory contract failed");
449
450 match &call_result {
451 ExecutionResult::Success { output, .. } => match output {
452 Output::Call(bytes) => {
453 if !bytes.is_empty() {
454 assert!(
455 bytes.iter().all(|&b| b == 0),
456 "When CREATE operation failed, it should return all zero address"
457 );
458 }
459 }
460 _ => panic!("unexpected output type"),
461 },
462 _ => panic!("execution result is not Success"),
463 }
464 }
465
466 #[test]
467 fn test_eip170_create_opcode_size_limit_success() {
468 let factory_code = vec![
488 0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
505
506 let factory_bytecode: Bytes = factory_code.into();
508 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
509 .expect("factory contract deployment failed");
510 let factory_address = match &factory_result {
512 ExecutionResult::Success {
513 output: Output::Create(_, Some(addr)),
514 ..
515 } => *addr,
516 _ => panic!("factory contract deployment failed: {factory_result:?}"),
517 };
518
519 let tx_caller = address!("0x0000000000000000000000000000000000100000");
521 let call_result = Context::mainnet()
522 .with_db(CacheDB::<EmptyDB>::default())
523 .build_mainnet()
524 .transact_commit(
525 TxEnv::builder()
526 .caller(tx_caller)
527 .kind(TxKind::Call(factory_address))
528 .data(Bytes::new())
529 .build()
530 .unwrap(),
531 )
532 .expect("call factory contract failed");
533
534 match &call_result {
535 ExecutionResult::Success { output, .. } => {
536 match output {
537 Output::Call(bytes) => {
538 if !bytes.is_empty() {
540 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
541 }
542 }
543 _ => panic!("unexpected output type"),
544 }
545 }
546 _ => panic!("execution result is not Success"),
547 }
548 }
549}