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 match TransactionType::from(tx_type) {
100 TransactionType::Legacy => {
101 // Check chain_id only if it is present in the legacy transaction.
102 // EIP-155: Simple replay attack protection
103 if let Some(chain_id) = tx.chain_id() {
104 if chain_id != context.cfg().chain_id() {
105 return Err(InvalidTransaction::InvalidChainId);
106 }
107 }
108 // Gas price must be at least the basefee.
109 if let Some(base_fee) = base_fee {
110 if tx.gas_price() < base_fee {
111 return Err(InvalidTransaction::GasPriceLessThanBasefee);
112 }
113 }
114 }
115 TransactionType::Eip2930 => {
116 // Enabled in BERLIN hardfork
117 if !spec_id.is_enabled_in(SpecId::BERLIN) {
118 return Err(InvalidTransaction::Eip2930NotSupported);
119 }
120
121 if Some(context.cfg().chain_id()) != tx.chain_id() {
122 return Err(InvalidTransaction::InvalidChainId);
123 }
124
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::Eip1559 => {
133 if !spec_id.is_enabled_in(SpecId::LONDON) {
134 return Err(InvalidTransaction::Eip1559NotSupported);
135 }
136
137 if Some(context.cfg().chain_id()) != tx.chain_id() {
138 return Err(InvalidTransaction::InvalidChainId);
139 }
140
141 validate_priority_fee_tx(
142 tx.max_fee_per_gas(),
143 tx.max_priority_fee_per_gas().unwrap_or_default(),
144 base_fee,
145 )?;
146 }
147 TransactionType::Eip4844 => {
148 if !spec_id.is_enabled_in(SpecId::CANCUN) {
149 return Err(InvalidTransaction::Eip4844NotSupported);
150 }
151
152 if Some(context.cfg().chain_id()) != tx.chain_id() {
153 return Err(InvalidTransaction::InvalidChainId);
154 }
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 )?;
161
162 validate_eip4844_tx(
163 tx.blob_versioned_hashes(),
164 tx.max_fee_per_blob_gas(),
165 context.block().blob_gasprice().unwrap_or_default(),
166 context.cfg().blob_max_count(),
167 )?;
168 }
169 TransactionType::Eip7702 => {
170 // Check if EIP-7702 transaction is enabled.
171 if !spec_id.is_enabled_in(SpecId::PRAGUE) {
172 return Err(InvalidTransaction::Eip7702NotSupported);
173 }
174
175 if Some(context.cfg().chain_id()) != tx.chain_id() {
176 return Err(InvalidTransaction::InvalidChainId);
177 }
178
179 validate_priority_fee_tx(
180 tx.max_fee_per_gas(),
181 tx.max_priority_fee_per_gas().unwrap_or_default(),
182 base_fee,
183 )?;
184
185 let auth_list_len = tx.authorization_list_len();
186 // The transaction is considered invalid if the length of authorization_list is zero.
187 if auth_list_len == 0 {
188 return Err(InvalidTransaction::EmptyAuthorizationList);
189 }
190 }
191 /* // TODO(EOF) EOF removed from spec.
192 TransactionType::Eip7873 => {
193 // Check if EIP-7873 transaction is enabled.
194 if !spec_id.is_enabled_in(SpecId::OSAKA) {
195 return Err(InvalidTransaction::Eip7873NotSupported);
196 }
197 // validate chain id
198 if Some(context.cfg().chain_id()) != tx.chain_id() {
199 return Err(InvalidTransaction::InvalidChainId);
200 }
201
202 // validate initcodes.
203 validate_eip7873_initcodes(tx.initcodes())?;
204
205 // InitcodeTransaction is invalid if the to is nil.
206 if tx.kind().is_create() {
207 return Err(InvalidTransaction::Eip7873MissingTarget);
208 }
209
210 validate_priority_fee_tx(
211 tx.max_fee_per_gas(),
212 tx.max_priority_fee_per_gas().unwrap_or_default(),
213 base_fee,
214 )?;
215 }
216 */
217 TransactionType::Custom => {
218 // Custom transaction type check is not done here.
219 }
220 };
221
222 // Check if gas_limit is more than block_gas_limit
223 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
224 {
225 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
226 }
227
228 // EIP-3860: Limit and meter initcode
229 if spec_id.is_enabled_in(SpecId::SHANGHAI) && tx.kind().is_create() {
230 let max_initcode_size = context.cfg().max_code_size().saturating_mul(2);
231 if context.tx().input().len() > max_initcode_size {
232 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
233 }
234 }
235
236 Ok(())
237}
238
239/* TODO(EOF)
240/// Validate Initcode Transaction initcode list, return error if any of the following conditions are met:
241/// * there are zero entries in initcodes, or if there are more than MAX_INITCODE_COUNT entries.
242/// * any entry in initcodes is zero length, or if any entry exceeds MAX_INITCODE_SIZE.
243/// * the to is nil.
244pub fn validate_eip7873_initcodes(initcodes: &[Bytes]) -> Result<(), InvalidTransaction> {
245 let mut i = 0;
246 for initcode in initcodes {
247 // InitcodeTransaction is invalid if any entry in initcodes is zero length
248 if initcode.is_empty() {
249 return Err(InvalidTransaction::Eip7873EmptyInitcode { i });
250 }
251
252 // or if any entry exceeds MAX_INITCODE_SIZE.
253 if initcode.len() > MAX_INITCODE_SIZE {
254 return Err(InvalidTransaction::Eip7873InitcodeTooLarge {
255 i,
256 size: initcode.len(),
257 });
258 }
259
260 i += 1;
261 }
262
263 // InitcodeTransaction is invalid if there are zero entries in initcodes,
264 if i == 0 {
265 return Err(InvalidTransaction::Eip7873EmptyInitcodeList);
266 }
267
268 // or if there are more than MAX_INITCODE_COUNT entries.
269 if i > MAX_INITCODE_COUNT {
270 return Err(InvalidTransaction::Eip7873TooManyInitcodes { size: i });
271 }
272
273 Ok(())
274}
275*/
276
277/// Validate initial transaction gas.
278pub fn validate_initial_tx_gas(
279 tx: impl Transaction,
280 spec: SpecId,
281) -> Result<InitialAndFloorGas, InvalidTransaction> {
282 let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
283
284 // Additional check to see if limit is big enough to cover initial gas.
285 if gas.initial_gas > tx.gas_limit() {
286 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
287 gas_limit: tx.gas_limit(),
288 initial_gas: gas.initial_gas,
289 });
290 }
291
292 // EIP-7623: Increase calldata cost
293 // floor gas should be less than gas limit.
294 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
295 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
296 gas_floor: gas.floor_gas,
297 gas_limit: tx.gas_limit(),
298 });
299 };
300
301 Ok(gas)
302}
303
304#[cfg(test)]
305mod tests {
306 use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
307 use bytecode::opcode;
308 use context::{
309 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
310 Context,
311 };
312 use database::{CacheDB, EmptyDB};
313 use primitives::{address, Address, Bytes, TxKind, MAX_INITCODE_SIZE};
314
315 fn deploy_contract(
316 bytecode: Bytes,
317 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
318 let ctx = Context::mainnet()
319 .modify_tx_chained(|tx| {
320 tx.kind = TxKind::Create;
321 tx.data = bytecode.clone();
322 })
323 .with_db(CacheDB::<EmptyDB>::default());
324
325 let mut evm = ctx.build_mainnet();
326 evm.replay_commit()
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 .modify_tx_chained(|tx| {
445 tx.caller = tx_caller;
446 tx.kind = TxKind::Call(factory_address);
447 tx.data = Bytes::new();
448 })
449 .with_db(CacheDB::<EmptyDB>::default())
450 .build_mainnet()
451 .replay_commit()
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 .modify_tx_chained(|tx| {
526 tx.caller = tx_caller;
527 tx.kind = TxKind::Call(factory_address);
528 tx.data = Bytes::new();
529 })
530 .with_db(CacheDB::<EmptyDB>::default())
531 .build_mainnet()
532 .replay_commit()
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}