revm_handler/validation.rs
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
10/// Validates the execution environment including block and transaction parameters.
11pub fn validate_env<CTX: ContextTr, ERROR: From<InvalidHeader> + From<InvalidTransaction>>(
12 context: CTX,
13) -> Result<(), ERROR> {
14 let spec = context.cfg().spec().into();
15 // `prevrandao` is required for the merge
16 if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
17 return Err(InvalidHeader::PrevrandaoNotSet.into());
18 }
19 // `excess_blob_gas` is required for Cancun
20 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
26/// Validate transaction that has EIP-1559 priority fee
27pub 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 // Or gas_max_fee for eip1559
35 return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
36 }
37
38 // Check minimal cost against basefee
39 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
49/// Validate EIP-4844 transaction.
50pub 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 // Ensure that the user was willing to at least pay the current blob gasprice
57 if block_blob_gas_price > max_blob_fee {
58 return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
59 }
60
61 // There must be at least one blob
62 if blobs.is_empty() {
63 return Err(InvalidTransaction::EmptyBlobs);
64 }
65
66 // All versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG
67 for blob in blobs {
68 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
69 return Err(InvalidTransaction::BlobVersionNotSupported);
70 }
71 }
72
73 // Ensure the total blob gas spent is at most equal to the limit
74 // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
75 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
86/// Validate transaction against block and configuration for mainnet.
87pub fn validate_tx_env<CTX: ContextTr, Error>(
88 context: CTX,
89 spec_id: SpecId,
90) -> Result<(), InvalidTransaction> {
91 // Check if the transaction's chain id is correct
92 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 // Check chain_id if config is enabled.
104 // EIP-155: Simple replay attack protection
105 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 // Legacy transaction are the only one that can omit chain_id.
112 return Err(InvalidTransaction::MissingChainId);
113 }
114 }
115
116 // EIP-7825: Transaction Gas Limit Cap
117 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 // Gas price must be at least the basefee.
130 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 // Enabled in BERLIN hardfork
138 if !spec_id.is_enabled_in(SpecId::BERLIN) {
139 return Err(InvalidTransaction::Eip2930NotSupported);
140 }
141
142 // Gas price must be at least the basefee.
143 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 // Check if EIP-7702 transaction is enabled.
181 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 // The transaction is considered invalid if the length of authorization_list is zero.
194 if auth_list_len == 0 {
195 return Err(InvalidTransaction::EmptyAuthorizationList);
196 }
197 }
198 /* // TODO(EOF) EOF removed from spec.
199 TransactionType::Eip7873 => {
200 // Check if EIP-7873 transaction is enabled.
201 if !spec_id.is_enabled_in(SpecId::OSAKA) {
202 return Err(InvalidTransaction::Eip7873NotSupported);
203 }
204 // validate chain id
205 if Some(context.cfg().chain_id()) != tx.chain_id() {
206 return Err(InvalidTransaction::InvalidChainId);
207 }
208
209 // validate initcodes.
210 validate_eip7873_initcodes(tx.initcodes())?;
211
212 // InitcodeTransaction is invalid if the to is nil.
213 if tx.kind().is_create() {
214 return Err(InvalidTransaction::Eip7873MissingTarget);
215 }
216
217 validate_priority_fee_tx(
218 tx.max_fee_per_gas(),
219 tx.max_priority_fee_per_gas().unwrap_or_default(),
220 base_fee,
221 )?;
222 }
223 */
224 TransactionType::Custom => {
225 // Custom transaction type check is not done here.
226 }
227 };
228
229 // Check if gas_limit is more than block_gas_limit
230 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
231 {
232 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
233 }
234
235 // EIP-3860: Limit and meter initcode. Still valid with EIP-7907 and increase of initcode size.
236 if spec_id.is_enabled_in(SpecId::SHANGHAI)
237 && tx.kind().is_create()
238 && context.tx().input().len() > context.cfg().max_initcode_size()
239 {
240 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
241 }
242
243 Ok(())
244}
245
246/* TODO(EOF)
247/// Validate Initcode Transaction initcode list, return error if any of the following conditions are met:
248/// * there are zero entries in initcodes, or if there are more than MAX_INITCODE_COUNT entries.
249/// * any entry in initcodes is zero length, or if any entry exceeds MAX_INITCODE_SIZE.
250/// * the to is nil.
251pub fn validate_eip7873_initcodes(initcodes: &[Bytes]) -> Result<(), InvalidTransaction> {
252 let mut i = 0;
253 for initcode in initcodes {
254 // InitcodeTransaction is invalid if any entry in initcodes is zero length
255 if initcode.is_empty() {
256 return Err(InvalidTransaction::Eip7873EmptyInitcode { i });
257 }
258
259 // or if any entry exceeds MAX_INITCODE_SIZE.
260 if initcode.len() > MAX_INITCODE_SIZE {
261 return Err(InvalidTransaction::Eip7873InitcodeTooLarge {
262 i,
263 size: initcode.len(),
264 });
265 }
266
267 i += 1;
268 }
269
270 // InitcodeTransaction is invalid if there are zero entries in initcodes,
271 if i == 0 {
272 return Err(InvalidTransaction::Eip7873EmptyInitcodeList);
273 }
274
275 // or if there are more than MAX_INITCODE_COUNT entries.
276 if i > MAX_INITCODE_COUNT {
277 return Err(InvalidTransaction::Eip7873TooManyInitcodes { size: i });
278 }
279
280 Ok(())
281}
282*/
283
284/// Validate initial transaction gas.
285pub fn validate_initial_tx_gas(
286 tx: impl Transaction,
287 spec: SpecId,
288) -> Result<InitialAndFloorGas, InvalidTransaction> {
289 let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
290
291 // Additional check to see if limit is big enough to cover initial gas.
292 if gas.initial_gas > tx.gas_limit() {
293 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
294 gas_limit: tx.gas_limit(),
295 initial_gas: gas.initial_gas,
296 });
297 }
298
299 // EIP-7623: Increase calldata cost
300 // floor gas should be less than gas limit.
301 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
302 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
303 gas_floor: gas.floor_gas,
304 gas_limit: tx.gas_limit(),
305 });
306 };
307
308 Ok(gas)
309}
310
311#[cfg(test)]
312mod tests {
313 use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
314 use bytecode::opcode;
315 use context::{
316 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
317 Context, TxEnv,
318 };
319 use database::{CacheDB, EmptyDB};
320 use primitives::{address, eip3860, eip7907, hardfork::SpecId, Bytes, TxKind};
321
322 fn deploy_contract(
323 bytecode: Bytes,
324 spec_id: Option<SpecId>,
325 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
326 let ctx = Context::mainnet()
327 .modify_cfg_chained(|c| {
328 if let Some(spec_id) = spec_id {
329 c.spec = spec_id;
330 }
331 })
332 .with_db(CacheDB::<EmptyDB>::default());
333
334 let mut evm = ctx.build_mainnet();
335 evm.transact_commit(
336 TxEnv::builder()
337 .kind(TxKind::Create)
338 .data(bytecode.clone())
339 .build()
340 .unwrap(),
341 )
342 }
343
344 #[test]
345 fn test_eip3860_initcode_size_limit_failure() {
346 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
347 let bytecode: Bytes = large_bytecode.into();
348 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
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_prague() {
359 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
360 let bytecode: Bytes = large_bytecode.into();
361 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
362 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
363 }
364
365 #[test]
366 fn test_eip7907_initcode_size_limit_failure_osaka() {
367 let large_bytecode = vec![opcode::STOP; eip7907::MAX_INITCODE_SIZE + 1];
368 let bytecode: Bytes = large_bytecode.into();
369 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
370 assert!(matches!(
371 result,
372 Err(EVMError::Transaction(
373 InvalidTransaction::CreateInitCodeSizeLimit
374 ))
375 ));
376 }
377
378 #[test]
379 fn test_eip7907_code_size_limit_failure() {
380 // EIP-7907: MAX_CODE_SIZE = 0x40000
381 // use the simplest method to return a contract code size greater than 0x40000
382 // PUSH3 0x40001 (greater than 0x40000) - return size
383 // PUSH1 0x00 - memory position 0
384 // RETURN - return uninitialized memory, will be filled with 0
385 let init_code = vec![
386 0x62, 0x04, 0x00, 0x01, // PUSH3 0x40001 (greater than 0x40000)
387 0x60, 0x00, // PUSH1 0
388 0xf3, // RETURN
389 ];
390 let bytecode: Bytes = init_code.into();
391 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
392 assert!(matches!(
393 result,
394 Ok(ExecutionResult::Halt {
395 reason: HaltReason::CreateContractSizeLimit,
396 ..
397 },)
398 ));
399 }
400
401 #[test]
402 fn test_eip170_code_size_limit_failure() {
403 // use the simplest method to return a contract code size greater than 0x6000
404 // PUSH3 0x6001 (greater than 0x6000) - return size
405 // PUSH1 0x00 - memory position 0
406 // RETURN - return uninitialized memory, will be filled with 0
407 let init_code = vec![
408 0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (greater than 0x6000)
409 0x60, 0x00, // PUSH1 0
410 0xf3, // RETURN
411 ];
412 let bytecode: Bytes = init_code.into();
413 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
414 assert!(matches!(
415 result,
416 Ok(ExecutionResult::Halt {
417 reason: HaltReason::CreateContractSizeLimit,
418 ..
419 },)
420 ));
421 }
422
423 #[test]
424 fn test_eip170_code_size_limit_success() {
425 // use the simplest method to return a contract code size equal to 0x6000
426 // PUSH3 0x6000 - return size
427 // PUSH1 0x00 - memory position 0
428 // RETURN - return uninitialized memory, will be filled with 0
429 let init_code = vec![
430 0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000
431 0x60, 0x00, // PUSH1 0
432 0xf3, // RETURN
433 ];
434 let bytecode: Bytes = init_code.into();
435 let result = deploy_contract(bytecode, None);
436 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
437 }
438
439 #[test]
440 fn test_eip170_create_opcode_size_limit_failure() {
441 // 1. create a "factory" contract, which will use the CREATE opcode to create another large contract
442 // 2. because the sub contract exceeds the EIP-170 limit, the CREATE operation should fail
443
444 // the bytecode of the factory contract:
445 // PUSH1 0x01 - the value for MSTORE
446 // PUSH1 0x00 - the memory position
447 // MSTORE - store a non-zero value at the beginning of memory
448
449 // PUSH3 0x6001 - the return size (exceeds 0x6000)
450 // PUSH1 0x00 - the memory offset
451 // PUSH1 0x00 - the amount of ETH sent
452 // CREATE - create contract instruction (create contract from current memory)
453
454 // PUSH1 0x00 - the return value storage position
455 // MSTORE - store the address returned by CREATE to the memory position 0
456 // PUSH1 0x20 - the return size (32 bytes)
457 // PUSH1 0x00 - the return offset
458 // RETURN - return the result
459
460 let factory_code = vec![
461 // 1. store a non-zero value at the beginning of memory
462 0x60, 0x01, // PUSH1 0x01
463 0x60, 0x00, // PUSH1 0x00
464 0x52, // MSTORE
465 // 2. prepare to create a large contract
466 0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (exceeds 0x6000)
467 0x60, 0x00, // PUSH1 0x00 (the memory offset)
468 0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
469 0xf0, // CREATE
470 // 3. store the address returned by CREATE to the memory position 0
471 0x60, 0x00, // PUSH1 0x00
472 0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
473 // 4. return the result
474 0x60, 0x20, // PUSH1 0x20 (32 bytes)
475 0x60, 0x00, // PUSH1 0x00
476 0xf3, // RETURN
477 ];
478
479 // deploy factory contract
480 let factory_bytecode: Bytes = factory_code.into();
481 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
482 .expect("factory contract deployment failed");
483
484 // get factory contract address
485 let factory_address = match &factory_result {
486 ExecutionResult::Success {
487 output: Output::Create(_, Some(addr)),
488 ..
489 } => *addr,
490 _ => panic!("factory contract deployment failed: {factory_result:?}"),
491 };
492
493 // call factory contract to create sub contract
494 let tx_caller = address!("0x0000000000000000000000000000000000100000");
495 let call_result = Context::mainnet()
496 .with_db(CacheDB::<EmptyDB>::default())
497 .build_mainnet()
498 .transact_commit(
499 TxEnv::builder()
500 .caller(tx_caller)
501 .kind(TxKind::Call(factory_address))
502 .data(Bytes::new())
503 .build()
504 .unwrap(),
505 )
506 .expect("call factory contract failed");
507
508 match &call_result {
509 ExecutionResult::Success { output, .. } => match output {
510 Output::Call(bytes) => {
511 if !bytes.is_empty() {
512 assert!(
513 bytes.iter().all(|&b| b == 0),
514 "When CREATE operation failed, it should return all zero address"
515 );
516 }
517 }
518 _ => panic!("unexpected output type"),
519 },
520 _ => panic!("execution result is not Success"),
521 }
522 }
523
524 #[test]
525 fn test_eip170_create_opcode_size_limit_success() {
526 // 1. create a "factory" contract, which will use the CREATE opcode to create another contract
527 // 2. the sub contract generated by the factory contract does not exceed the EIP-170 limit, so it should be created successfully
528
529 // the bytecode of the factory contract:
530 // PUSH1 0x01 - the value for MSTORE
531 // PUSH1 0x00 - the memory position
532 // MSTORE - store a non-zero value at the beginning of memory
533
534 // PUSH3 0x6000 - the return size (0x6000)
535 // PUSH1 0x00 - the memory offset
536 // PUSH1 0x00 - the amount of ETH sent
537 // CREATE - create contract instruction (create contract from current memory)
538
539 // PUSH1 0x00 - the return value storage position
540 // MSTORE - store the address returned by CREATE to the memory position 0
541 // PUSH1 0x20 - the return size (32 bytes)
542 // PUSH1 0x00 - the return offset
543 // RETURN - return the result
544
545 let factory_code = vec![
546 // 1. store a non-zero value at the beginning of memory
547 0x60, 0x01, // PUSH1 0x01
548 0x60, 0x00, // PUSH1 0x00
549 0x52, // MSTORE
550 // 2. prepare to create a contract
551 0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000 (0x6000)
552 0x60, 0x00, // PUSH1 0x00 (the memory offset)
553 0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
554 0xf0, // CREATE
555 // 3. store the address returned by CREATE to the memory position 0
556 0x60, 0x00, // PUSH1 0x00
557 0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
558 // 4. return the result
559 0x60, 0x20, // PUSH1 0x20 (32 bytes)
560 0x60, 0x00, // PUSH1 0x00
561 0xf3, // RETURN
562 ];
563
564 // deploy factory contract
565 let factory_bytecode: Bytes = factory_code.into();
566 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
567 .expect("factory contract deployment failed");
568 // get factory contract address
569 let factory_address = match &factory_result {
570 ExecutionResult::Success {
571 output: Output::Create(_, Some(addr)),
572 ..
573 } => *addr,
574 _ => panic!("factory contract deployment failed: {factory_result:?}"),
575 };
576
577 // call factory contract to create sub contract
578 let tx_caller = address!("0x0000000000000000000000000000000000100000");
579 let call_result = Context::mainnet()
580 .with_db(CacheDB::<EmptyDB>::default())
581 .build_mainnet()
582 .transact_commit(
583 TxEnv::builder()
584 .caller(tx_caller)
585 .kind(TxKind::Call(factory_address))
586 .data(Bytes::new())
587 .build()
588 .unwrap(),
589 )
590 .expect("call factory contract failed");
591
592 match &call_result {
593 ExecutionResult::Success { output, .. } => {
594 match output {
595 Output::Call(bytes) => {
596 // check if CREATE operation is successful (return non-zero address)
597 if !bytes.is_empty() {
598 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
599 }
600 }
601 _ => panic!("unexpected output type"),
602 }
603 }
604 _ => panic!("execution result is not Success"),
605 }
606 }
607}