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_tx_chained(|tx| {
328 tx.kind = TxKind::Create;
329 tx.data = bytecode.clone();
330 })
331 .modify_cfg_chained(|c| {
332 if let Some(spec_id) = spec_id {
333 c.spec = spec_id;
334 }
335 })
336 .with_db(CacheDB::<EmptyDB>::default());
337
338 let mut evm = ctx.build_mainnet();
339 evm.transact_commit(TxEnv {
340 kind: TxKind::Create,
341 data: bytecode.clone(),
342 ..Default::default()
343 })
344 }
345
346 #[test]
347 fn test_eip3860_initcode_size_limit_failure() {
348 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE + 1];
349 let bytecode: Bytes = large_bytecode.into();
350 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
351 assert!(matches!(
352 result,
353 Err(EVMError::Transaction(
354 InvalidTransaction::CreateInitCodeSizeLimit
355 ))
356 ));
357 }
358
359 #[test]
360 fn test_eip3860_initcode_size_limit_success_prague() {
361 let large_bytecode = vec![opcode::STOP; eip3860::MAX_INITCODE_SIZE];
362 let bytecode: Bytes = large_bytecode.into();
363 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
364 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
365 }
366
367 #[test]
368 fn test_eip7907_initcode_size_limit_failure_osaka() {
369 let large_bytecode = vec![opcode::STOP; eip7907::MAX_INITCODE_SIZE + 1];
370 let bytecode: Bytes = large_bytecode.into();
371 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
372 assert!(matches!(
373 result,
374 Err(EVMError::Transaction(
375 InvalidTransaction::CreateInitCodeSizeLimit
376 ))
377 ));
378 }
379
380 #[test]
381 fn test_eip7907_code_size_limit_failure() {
382 // EIP-7907: MAX_CODE_SIZE = 0x40000
383 // use the simplest method to return a contract code size greater than 0x40000
384 // PUSH3 0x40001 (greater than 0x40000) - return size
385 // PUSH1 0x00 - memory position 0
386 // RETURN - return uninitialized memory, will be filled with 0
387 let init_code = vec![
388 0x62, 0x04, 0x00, 0x01, // PUSH3 0x40001 (greater than 0x40000)
389 0x60, 0x00, // PUSH1 0
390 0xf3, // RETURN
391 ];
392 let bytecode: Bytes = init_code.into();
393 let result = deploy_contract(bytecode, Some(SpecId::OSAKA));
394 assert!(matches!(
395 result,
396 Ok(ExecutionResult::Halt {
397 reason: HaltReason::CreateContractSizeLimit,
398 ..
399 },)
400 ));
401 }
402
403 #[test]
404 fn test_eip170_code_size_limit_failure() {
405 // use the simplest method to return a contract code size greater than 0x6000
406 // PUSH3 0x6001 (greater than 0x6000) - return size
407 // PUSH1 0x00 - memory position 0
408 // RETURN - return uninitialized memory, will be filled with 0
409 let init_code = vec![
410 0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (greater than 0x6000)
411 0x60, 0x00, // PUSH1 0
412 0xf3, // RETURN
413 ];
414 let bytecode: Bytes = init_code.into();
415 let result = deploy_contract(bytecode, Some(SpecId::PRAGUE));
416 assert!(matches!(
417 result,
418 Ok(ExecutionResult::Halt {
419 reason: HaltReason::CreateContractSizeLimit,
420 ..
421 },)
422 ));
423 }
424
425 #[test]
426 fn test_eip170_code_size_limit_success() {
427 // use the simplest method to return a contract code size equal to 0x6000
428 // PUSH3 0x6000 - return size
429 // PUSH1 0x00 - memory position 0
430 // RETURN - return uninitialized memory, will be filled with 0
431 let init_code = vec![
432 0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000
433 0x60, 0x00, // PUSH1 0
434 0xf3, // RETURN
435 ];
436 let bytecode: Bytes = init_code.into();
437 let result = deploy_contract(bytecode, None);
438 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
439 }
440
441 #[test]
442 fn test_eip170_create_opcode_size_limit_failure() {
443 // 1. create a "factory" contract, which will use the CREATE opcode to create another large contract
444 // 2. because the sub contract exceeds the EIP-170 limit, the CREATE operation should fail
445
446 // the bytecode of the factory contract:
447 // PUSH1 0x01 - the value for MSTORE
448 // PUSH1 0x00 - the memory position
449 // MSTORE - store a non-zero value at the beginning of memory
450
451 // PUSH3 0x6001 - the return size (exceeds 0x6000)
452 // PUSH1 0x00 - the memory offset
453 // PUSH1 0x00 - the amount of ETH sent
454 // CREATE - create contract instruction (create contract from current memory)
455
456 // PUSH1 0x00 - the return value storage position
457 // MSTORE - store the address returned by CREATE to the memory position 0
458 // PUSH1 0x20 - the return size (32 bytes)
459 // PUSH1 0x00 - the return offset
460 // RETURN - return the result
461
462 let factory_code = vec![
463 // 1. store a non-zero value at the beginning of memory
464 0x60, 0x01, // PUSH1 0x01
465 0x60, 0x00, // PUSH1 0x00
466 0x52, // MSTORE
467 // 2. prepare to create a large contract
468 0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (exceeds 0x6000)
469 0x60, 0x00, // PUSH1 0x00 (the memory offset)
470 0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
471 0xf0, // CREATE
472 // 3. store the address returned by CREATE to the memory position 0
473 0x60, 0x00, // PUSH1 0x00
474 0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
475 // 4. return the result
476 0x60, 0x20, // PUSH1 0x20 (32 bytes)
477 0x60, 0x00, // PUSH1 0x00
478 0xf3, // RETURN
479 ];
480
481 // deploy factory contract
482 let factory_bytecode: Bytes = factory_code.into();
483 let factory_result = deploy_contract(factory_bytecode, Some(SpecId::PRAGUE))
484 .expect("factory contract deployment failed");
485
486 // get factory contract address
487 let factory_address = match &factory_result {
488 ExecutionResult::Success {
489 output: Output::Create(_, Some(addr)),
490 ..
491 } => *addr,
492 _ => panic!("factory contract deployment failed: {factory_result:?}"),
493 };
494
495 // call factory contract to create sub contract
496 let tx_caller = address!("0x0000000000000000000000000000000000100000");
497 let call_result = Context::mainnet()
498 .with_db(CacheDB::<EmptyDB>::default())
499 .build_mainnet()
500 .transact_commit(TxEnv {
501 caller: tx_caller,
502 kind: TxKind::Call(factory_address),
503 data: Bytes::new(),
504 ..Default::default()
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(TxEnv {
583 caller: tx_caller,
584 kind: TxKind::Call(factory_address),
585 data: Bytes::new(),
586 ..Default::default()
587 })
588 .expect("call factory contract failed");
589
590 match &call_result {
591 ExecutionResult::Success { output, .. } => {
592 match output {
593 Output::Call(bytes) => {
594 // check if CREATE operation is successful (return non-zero address)
595 if !bytes.is_empty() {
596 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
597 }
598 }
599 _ => panic!("unexpected output type"),
600 }
601 }
602 _ => panic!("execution result is not Success"),
603 }
604 }
605}