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
10pub fn validate_env<CTX: ContextTr, ERROR: From<InvalidHeader> + From<InvalidTransaction>>(
11 context: CTX,
12) -> Result<(), ERROR> {
13 let spec = context.cfg().spec().into();
14 // `prevrandao` is required for the merge
15 if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
16 return Err(InvalidHeader::PrevrandaoNotSet.into());
17 }
18 // `excess_blob_gas` is required for Cancun
19 if spec.is_enabled_in(SpecId::CANCUN) && context.block().blob_excess_gas_and_price().is_none() {
20 return Err(InvalidHeader::ExcessBlobGasNotSet.into());
21 }
22 validate_tx_env::<CTX, InvalidTransaction>(context, spec).map_err(Into::into)
23}
24
25/// Validate transaction that has EIP-1559 priority fee
26pub fn validate_priority_fee_tx(
27 max_fee: u128,
28 max_priority_fee: u128,
29 base_fee: Option<u128>,
30) -> Result<(), InvalidTransaction> {
31 if max_priority_fee > max_fee {
32 // Or gas_max_fee for eip1559
33 return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
34 }
35
36 // Check minimal cost against basefee
37 if let Some(base_fee) = base_fee {
38 let effective_gas_price = cmp::min(max_fee, base_fee.saturating_add(max_priority_fee));
39 if effective_gas_price < base_fee {
40 return Err(InvalidTransaction::GasPriceLessThanBasefee);
41 }
42 }
43
44 Ok(())
45}
46
47/// Validate EIP-4844 transaction.
48pub fn validate_eip4844_tx(
49 blobs: &[B256],
50 max_blob_fee: u128,
51 block_blob_gas_price: u128,
52 max_blobs: Option<u64>,
53) -> Result<(), InvalidTransaction> {
54 // Ensure that the user was willing to at least pay the current blob gasprice
55 if block_blob_gas_price > max_blob_fee {
56 return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
57 }
58
59 // There must be at least one blob
60 if blobs.is_empty() {
61 return Err(InvalidTransaction::EmptyBlobs);
62 }
63
64 // All versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG
65 for blob in blobs {
66 if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
67 return Err(InvalidTransaction::BlobVersionNotSupported);
68 }
69 }
70
71 // Ensure the total blob gas spent is at most equal to the limit
72 // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
73 if let Some(max_blobs) = max_blobs {
74 if blobs.len() > max_blobs as usize {
75 return Err(InvalidTransaction::TooManyBlobs {
76 have: blobs.len(),
77 max: max_blobs as usize,
78 });
79 }
80 }
81 Ok(())
82}
83
84/// Validate transaction against block and configuration for mainnet.
85pub fn validate_tx_env<CTX: ContextTr, Error>(
86 context: CTX,
87 spec_id: SpecId,
88) -> Result<(), InvalidTransaction> {
89 // Check if the transaction's chain id is correct
90 let tx_type = context.tx().tx_type();
91 let tx = context.tx();
92
93 let base_fee = if context.cfg().is_base_fee_check_disabled() {
94 None
95 } else {
96 Some(context.block().basefee() as u128)
97 };
98
99 let tx_type = TransactionType::from(tx_type);
100
101 // Check chain_id if config is enabled.
102 // EIP-155: Simple replay attack protection
103 if context.cfg().tx_chain_id_check() {
104 if let Some(chain_id) = tx.chain_id() {
105 if chain_id != context.cfg().chain_id() {
106 return Err(InvalidTransaction::InvalidChainId);
107 }
108 } else if !tx_type.is_legacy() && !tx_type.is_custom() {
109 // Legacy transaction are the only one that can omit chain_id.
110 return Err(InvalidTransaction::MissingChainId);
111 }
112 }
113
114 // EIP-7825: Transaction Gas Limit Cap
115 let cap = context.cfg().tx_gas_limit_cap();
116 if tx.gas_limit() > cap {
117 return Err(InvalidTransaction::TxGasLimitGreaterThanCap {
118 gas_limit: tx.gas_limit(),
119 cap,
120 });
121 }
122
123 match tx_type {
124 TransactionType::Legacy => {
125 // Gas price must be at least the basefee.
126 if let Some(base_fee) = base_fee {
127 if tx.gas_price() < base_fee {
128 return Err(InvalidTransaction::GasPriceLessThanBasefee);
129 }
130 }
131 }
132 TransactionType::Eip2930 => {
133 // Enabled in BERLIN hardfork
134 if !spec_id.is_enabled_in(SpecId::BERLIN) {
135 return Err(InvalidTransaction::Eip2930NotSupported);
136 }
137
138 // Gas price must be at least the basefee.
139 if let Some(base_fee) = base_fee {
140 if tx.gas_price() < base_fee {
141 return Err(InvalidTransaction::GasPriceLessThanBasefee);
142 }
143 }
144 }
145 TransactionType::Eip1559 => {
146 if !spec_id.is_enabled_in(SpecId::LONDON) {
147 return Err(InvalidTransaction::Eip1559NotSupported);
148 }
149
150 validate_priority_fee_tx(
151 tx.max_fee_per_gas(),
152 tx.max_priority_fee_per_gas().unwrap_or_default(),
153 base_fee,
154 )?;
155 }
156 TransactionType::Eip4844 => {
157 if !spec_id.is_enabled_in(SpecId::CANCUN) {
158 return Err(InvalidTransaction::Eip4844NotSupported);
159 }
160
161 validate_priority_fee_tx(
162 tx.max_fee_per_gas(),
163 tx.max_priority_fee_per_gas().unwrap_or_default(),
164 base_fee,
165 )?;
166
167 validate_eip4844_tx(
168 tx.blob_versioned_hashes(),
169 tx.max_fee_per_blob_gas(),
170 context.block().blob_gasprice().unwrap_or_default(),
171 context.cfg().blob_max_count(),
172 )?;
173 }
174 TransactionType::Eip7702 => {
175 // Check if EIP-7702 transaction is enabled.
176 if !spec_id.is_enabled_in(SpecId::PRAGUE) {
177 return Err(InvalidTransaction::Eip7702NotSupported);
178 }
179
180 validate_priority_fee_tx(
181 tx.max_fee_per_gas(),
182 tx.max_priority_fee_per_gas().unwrap_or_default(),
183 base_fee,
184 )?;
185
186 let auth_list_len = tx.authorization_list_len();
187 // The transaction is considered invalid if the length of authorization_list is zero.
188 if auth_list_len == 0 {
189 return Err(InvalidTransaction::EmptyAuthorizationList);
190 }
191 }
192 /* // TODO(EOF) EOF removed from spec.
193 TransactionType::Eip7873 => {
194 // Check if EIP-7873 transaction is enabled.
195 if !spec_id.is_enabled_in(SpecId::OSAKA) {
196 return Err(InvalidTransaction::Eip7873NotSupported);
197 }
198 // validate chain id
199 if Some(context.cfg().chain_id()) != tx.chain_id() {
200 return Err(InvalidTransaction::InvalidChainId);
201 }
202
203 // validate initcodes.
204 validate_eip7873_initcodes(tx.initcodes())?;
205
206 // InitcodeTransaction is invalid if the to is nil.
207 if tx.kind().is_create() {
208 return Err(InvalidTransaction::Eip7873MissingTarget);
209 }
210
211 validate_priority_fee_tx(
212 tx.max_fee_per_gas(),
213 tx.max_priority_fee_per_gas().unwrap_or_default(),
214 base_fee,
215 )?;
216 }
217 */
218 TransactionType::Custom => {
219 // Custom transaction type check is not done here.
220 }
221 };
222
223 // Check if gas_limit is more than block_gas_limit
224 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
225 {
226 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
227 }
228
229 // EIP-3860: Limit and meter initcode
230 if spec_id.is_enabled_in(SpecId::SHANGHAI) && tx.kind().is_create() {
231 let max_initcode_size = context.cfg().max_code_size().saturating_mul(2);
232 if context.tx().input().len() > max_initcode_size {
233 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
234 }
235 }
236
237 Ok(())
238}
239
240/* TODO(EOF)
241/// Validate Initcode Transaction initcode list, return error if any of the following conditions are met:
242/// * there are zero entries in initcodes, or if there are more than MAX_INITCODE_COUNT entries.
243/// * any entry in initcodes is zero length, or if any entry exceeds MAX_INITCODE_SIZE.
244/// * the to is nil.
245pub fn validate_eip7873_initcodes(initcodes: &[Bytes]) -> Result<(), InvalidTransaction> {
246 let mut i = 0;
247 for initcode in initcodes {
248 // InitcodeTransaction is invalid if any entry in initcodes is zero length
249 if initcode.is_empty() {
250 return Err(InvalidTransaction::Eip7873EmptyInitcode { i });
251 }
252
253 // or if any entry exceeds MAX_INITCODE_SIZE.
254 if initcode.len() > MAX_INITCODE_SIZE {
255 return Err(InvalidTransaction::Eip7873InitcodeTooLarge {
256 i,
257 size: initcode.len(),
258 });
259 }
260
261 i += 1;
262 }
263
264 // InitcodeTransaction is invalid if there are zero entries in initcodes,
265 if i == 0 {
266 return Err(InvalidTransaction::Eip7873EmptyInitcodeList);
267 }
268
269 // or if there are more than MAX_INITCODE_COUNT entries.
270 if i > MAX_INITCODE_COUNT {
271 return Err(InvalidTransaction::Eip7873TooManyInitcodes { size: i });
272 }
273
274 Ok(())
275}
276*/
277
278/// Validate initial transaction gas.
279pub fn validate_initial_tx_gas(
280 tx: impl Transaction,
281 spec: SpecId,
282) -> Result<InitialAndFloorGas, InvalidTransaction> {
283 let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
284
285 // Additional check to see if limit is big enough to cover initial gas.
286 if gas.initial_gas > tx.gas_limit() {
287 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
288 gas_limit: tx.gas_limit(),
289 initial_gas: gas.initial_gas,
290 });
291 }
292
293 // EIP-7623: Increase calldata cost
294 // floor gas should be less than gas limit.
295 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
296 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
297 gas_floor: gas.floor_gas,
298 gas_limit: tx.gas_limit(),
299 });
300 };
301
302 Ok(gas)
303}
304
305#[cfg(test)]
306mod tests {
307 use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
308 use bytecode::opcode;
309 use context::{
310 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
311 Context, TxEnv,
312 };
313 use database::{CacheDB, EmptyDB};
314 use primitives::{address, Address, Bytes, TxKind, MAX_INITCODE_SIZE};
315
316 fn deploy_contract(
317 bytecode: Bytes,
318 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
319 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
320
321 let mut evm = ctx.build_mainnet();
322 evm.transact_commit(TxEnv {
323 kind: TxKind::Create,
324 data: bytecode.clone(),
325 ..Default::default()
326 })
327 }
328
329 #[test]
330 fn test_eip3860_initcode_size_limit_failure() {
331 let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE + 1];
332 let bytecode: Bytes = large_bytecode.into();
333 let result = deploy_contract(bytecode);
334 assert!(matches!(
335 result,
336 Err(EVMError::Transaction(
337 InvalidTransaction::CreateInitCodeSizeLimit
338 ))
339 ));
340 }
341
342 #[test]
343 fn test_eip3860_initcode_size_limit_success() {
344 let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE];
345 let bytecode: Bytes = large_bytecode.into();
346 let result = deploy_contract(bytecode);
347 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
348 }
349
350 #[test]
351 fn test_eip170_code_size_limit_failure() {
352 // use the simplest method to return a contract code size greater than 0x6000
353 // PUSH3 0x6001 (greater than 0x6000) - return size
354 // PUSH1 0x00 - memory position 0
355 // RETURN - return uninitialized memory, will be filled with 0
356 let init_code = vec![
357 0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (greater than 0x6000)
358 0x60, 0x00, // PUSH1 0
359 0xf3, // RETURN
360 ];
361 let bytecode: Bytes = init_code.into();
362 let result = deploy_contract(bytecode);
363 assert!(matches!(
364 result,
365 Ok(ExecutionResult::Halt {
366 reason: HaltReason::CreateContractSizeLimit,
367 ..
368 },)
369 ));
370 }
371
372 #[test]
373 fn test_eip170_code_size_limit_success() {
374 // use the simplest method to return a contract code size equal to 0x6000
375 // PUSH3 0x6000 - return size
376 // PUSH1 0x00 - memory position 0
377 // RETURN - return uninitialized memory, will be filled with 0
378 let init_code = vec![
379 0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000
380 0x60, 0x00, // PUSH1 0
381 0xf3, // RETURN
382 ];
383 let bytecode: Bytes = init_code.into();
384 let result = deploy_contract(bytecode);
385 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
386 }
387
388 #[test]
389 fn test_eip170_create_opcode_size_limit_failure() {
390 // 1. create a "factory" contract, which will use the CREATE opcode to create another large contract
391 // 2. because the sub contract exceeds the EIP-170 limit, the CREATE operation should fail
392
393 // the bytecode of the factory contract:
394 // PUSH1 0x01 - the value for MSTORE
395 // PUSH1 0x00 - the memory position
396 // MSTORE - store a non-zero value at the beginning of memory
397
398 // PUSH3 0x6001 - the return size (exceeds 0x6000)
399 // PUSH1 0x00 - the memory offset
400 // PUSH1 0x00 - the amount of ETH sent
401 // CREATE - create contract instruction (create contract from current memory)
402
403 // PUSH1 0x00 - the return value storage position
404 // MSTORE - store the address returned by CREATE to the memory position 0
405 // PUSH1 0x20 - the return size (32 bytes)
406 // PUSH1 0x00 - the return offset
407 // RETURN - return the result
408
409 let factory_code = vec![
410 // 1. store a non-zero value at the beginning of memory
411 0x60, 0x01, // PUSH1 0x01
412 0x60, 0x00, // PUSH1 0x00
413 0x52, // MSTORE
414 // 2. prepare to create a large contract
415 0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (exceeds 0x6000)
416 0x60, 0x00, // PUSH1 0x00 (the memory offset)
417 0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
418 0xf0, // CREATE
419 // 3. store the address returned by CREATE to the memory position 0
420 0x60, 0x00, // PUSH1 0x00
421 0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
422 // 4. return the result
423 0x60, 0x20, // PUSH1 0x20 (32 bytes)
424 0x60, 0x00, // PUSH1 0x00
425 0xf3, // RETURN
426 ];
427
428 // deploy factory contract
429 let factory_bytecode: Bytes = factory_code.into();
430 let factory_result =
431 deploy_contract(factory_bytecode).expect("factory contract deployment failed");
432
433 // get factory contract address
434 let factory_address = match &factory_result {
435 ExecutionResult::Success { output, .. } => match output {
436 Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
437 },
438 _ => panic!("factory contract deployment failed"),
439 };
440
441 // call factory contract to create sub contract
442 let tx_caller = address!("0x0000000000000000000000000000000000100000");
443 let call_result = Context::mainnet()
444 .with_db(CacheDB::<EmptyDB>::default())
445 .build_mainnet()
446 .transact_commit(TxEnv {
447 caller: tx_caller,
448 kind: TxKind::Call(factory_address),
449 data: Bytes::new(),
450 ..Default::default()
451 })
452 .expect("call factory contract failed");
453
454 match &call_result {
455 ExecutionResult::Success { output, .. } => match output {
456 Output::Call(bytes) => {
457 if !bytes.is_empty() {
458 assert!(
459 bytes.iter().all(|&b| b == 0),
460 "When CREATE operation failed, it should return all zero address"
461 );
462 }
463 }
464 _ => panic!("unexpected output type"),
465 },
466 _ => panic!("execution result is not Success"),
467 }
468 }
469
470 #[test]
471 fn test_eip170_create_opcode_size_limit_success() {
472 // 1. create a "factory" contract, which will use the CREATE opcode to create another contract
473 // 2. the sub contract generated by the factory contract does not exceed the EIP-170 limit, so it should be created successfully
474
475 // the bytecode of the factory contract:
476 // PUSH1 0x01 - the value for MSTORE
477 // PUSH1 0x00 - the memory position
478 // MSTORE - store a non-zero value at the beginning of memory
479
480 // PUSH3 0x6000 - the return size (0x6000)
481 // PUSH1 0x00 - the memory offset
482 // PUSH1 0x00 - the amount of ETH sent
483 // CREATE - create contract instruction (create contract from current memory)
484
485 // PUSH1 0x00 - the return value storage position
486 // MSTORE - store the address returned by CREATE to the memory position 0
487 // PUSH1 0x20 - the return size (32 bytes)
488 // PUSH1 0x00 - the return offset
489 // RETURN - return the result
490
491 let factory_code = vec![
492 // 1. store a non-zero value at the beginning of memory
493 0x60, 0x01, // PUSH1 0x01
494 0x60, 0x00, // PUSH1 0x00
495 0x52, // MSTORE
496 // 2. prepare to create a contract
497 0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000 (0x6000)
498 0x60, 0x00, // PUSH1 0x00 (the memory offset)
499 0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
500 0xf0, // CREATE
501 // 3. store the address returned by CREATE to the memory position 0
502 0x60, 0x00, // PUSH1 0x00
503 0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
504 // 4. return the result
505 0x60, 0x20, // PUSH1 0x20 (32 bytes)
506 0x60, 0x00, // PUSH1 0x00
507 0xf3, // RETURN
508 ];
509
510 // deploy factory contract
511 let factory_bytecode: Bytes = factory_code.into();
512 let factory_result =
513 deploy_contract(factory_bytecode).expect("factory contract deployment failed");
514 // get factory contract address
515 let factory_address = match &factory_result {
516 ExecutionResult::Success { output, .. } => match output {
517 Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
518 },
519 _ => panic!("factory contract deployment failed"),
520 };
521
522 // call factory contract to create sub contract
523 let tx_caller = address!("0x0000000000000000000000000000000000100000");
524 let call_result = Context::mainnet()
525 .with_db(CacheDB::<EmptyDB>::default())
526 .build_mainnet()
527 .transact_commit(TxEnv {
528 caller: tx_caller,
529 kind: TxKind::Call(factory_address),
530 data: Bytes::new(),
531 ..Default::default()
532 })
533 .expect("call factory contract failed");
534
535 match &call_result {
536 ExecutionResult::Success { output, .. } => {
537 match output {
538 Output::Call(bytes) => {
539 // check if CREATE operation is successful (return non-zero address)
540 if !bytes.is_empty() {
541 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
542 }
543 }
544 _ => panic!("unexpected output type"),
545 }
546 }
547 _ => panic!("execution result is not Success"),
548 }
549 }
550}