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 match tx_type {
115 TransactionType::Legacy => {
116 // Gas price must be at least the basefee.
117 if let Some(base_fee) = base_fee {
118 if tx.gas_price() < base_fee {
119 return Err(InvalidTransaction::GasPriceLessThanBasefee);
120 }
121 }
122 }
123 TransactionType::Eip2930 => {
124 // Enabled in BERLIN hardfork
125 if !spec_id.is_enabled_in(SpecId::BERLIN) {
126 return Err(InvalidTransaction::Eip2930NotSupported);
127 }
128
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::Eip1559 => {
137 if !spec_id.is_enabled_in(SpecId::LONDON) {
138 return Err(InvalidTransaction::Eip1559NotSupported);
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 validate_priority_fee_tx(
153 tx.max_fee_per_gas(),
154 tx.max_priority_fee_per_gas().unwrap_or_default(),
155 base_fee,
156 )?;
157
158 validate_eip4844_tx(
159 tx.blob_versioned_hashes(),
160 tx.max_fee_per_blob_gas(),
161 context.block().blob_gasprice().unwrap_or_default(),
162 context.cfg().blob_max_count(),
163 )?;
164 }
165 TransactionType::Eip7702 => {
166 // Check if EIP-7702 transaction is enabled.
167 if !spec_id.is_enabled_in(SpecId::PRAGUE) {
168 return Err(InvalidTransaction::Eip7702NotSupported);
169 }
170
171 validate_priority_fee_tx(
172 tx.max_fee_per_gas(),
173 tx.max_priority_fee_per_gas().unwrap_or_default(),
174 base_fee,
175 )?;
176
177 let auth_list_len = tx.authorization_list_len();
178 // The transaction is considered invalid if the length of authorization_list is zero.
179 if auth_list_len == 0 {
180 return Err(InvalidTransaction::EmptyAuthorizationList);
181 }
182 }
183 /* // TODO(EOF) EOF removed from spec.
184 TransactionType::Eip7873 => {
185 // Check if EIP-7873 transaction is enabled.
186 if !spec_id.is_enabled_in(SpecId::OSAKA) {
187 return Err(InvalidTransaction::Eip7873NotSupported);
188 }
189 // validate chain id
190 if Some(context.cfg().chain_id()) != tx.chain_id() {
191 return Err(InvalidTransaction::InvalidChainId);
192 }
193
194 // validate initcodes.
195 validate_eip7873_initcodes(tx.initcodes())?;
196
197 // InitcodeTransaction is invalid if the to is nil.
198 if tx.kind().is_create() {
199 return Err(InvalidTransaction::Eip7873MissingTarget);
200 }
201
202 validate_priority_fee_tx(
203 tx.max_fee_per_gas(),
204 tx.max_priority_fee_per_gas().unwrap_or_default(),
205 base_fee,
206 )?;
207 }
208 */
209 TransactionType::Custom => {
210 // Custom transaction type check is not done here.
211 }
212 };
213
214 // Check if gas_limit is more than block_gas_limit
215 if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
216 {
217 return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
218 }
219
220 // EIP-3860: Limit and meter initcode
221 if spec_id.is_enabled_in(SpecId::SHANGHAI) && tx.kind().is_create() {
222 let max_initcode_size = context.cfg().max_code_size().saturating_mul(2);
223 if context.tx().input().len() > max_initcode_size {
224 return Err(InvalidTransaction::CreateInitCodeSizeLimit);
225 }
226 }
227
228 Ok(())
229}
230
231/* TODO(EOF)
232/// Validate Initcode Transaction initcode list, return error if any of the following conditions are met:
233/// * there are zero entries in initcodes, or if there are more than MAX_INITCODE_COUNT entries.
234/// * any entry in initcodes is zero length, or if any entry exceeds MAX_INITCODE_SIZE.
235/// * the to is nil.
236pub fn validate_eip7873_initcodes(initcodes: &[Bytes]) -> Result<(), InvalidTransaction> {
237 let mut i = 0;
238 for initcode in initcodes {
239 // InitcodeTransaction is invalid if any entry in initcodes is zero length
240 if initcode.is_empty() {
241 return Err(InvalidTransaction::Eip7873EmptyInitcode { i });
242 }
243
244 // or if any entry exceeds MAX_INITCODE_SIZE.
245 if initcode.len() > MAX_INITCODE_SIZE {
246 return Err(InvalidTransaction::Eip7873InitcodeTooLarge {
247 i,
248 size: initcode.len(),
249 });
250 }
251
252 i += 1;
253 }
254
255 // InitcodeTransaction is invalid if there are zero entries in initcodes,
256 if i == 0 {
257 return Err(InvalidTransaction::Eip7873EmptyInitcodeList);
258 }
259
260 // or if there are more than MAX_INITCODE_COUNT entries.
261 if i > MAX_INITCODE_COUNT {
262 return Err(InvalidTransaction::Eip7873TooManyInitcodes { size: i });
263 }
264
265 Ok(())
266}
267*/
268
269/// Validate initial transaction gas.
270pub fn validate_initial_tx_gas(
271 tx: impl Transaction,
272 spec: SpecId,
273) -> Result<InitialAndFloorGas, InvalidTransaction> {
274 let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
275
276 // Additional check to see if limit is big enough to cover initial gas.
277 if gas.initial_gas > tx.gas_limit() {
278 return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
279 gas_limit: tx.gas_limit(),
280 initial_gas: gas.initial_gas,
281 });
282 }
283
284 // EIP-7623: Increase calldata cost
285 // floor gas should be less than gas limit.
286 if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
287 return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
288 gas_floor: gas.floor_gas,
289 gas_limit: tx.gas_limit(),
290 });
291 };
292
293 Ok(gas)
294}
295
296#[cfg(test)]
297mod tests {
298 use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
299 use bytecode::opcode;
300 use context::{
301 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
302 Context, TxEnv,
303 };
304 use database::{CacheDB, EmptyDB};
305 use primitives::{address, Address, Bytes, TxKind, MAX_INITCODE_SIZE};
306
307 fn deploy_contract(
308 bytecode: Bytes,
309 ) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
310 let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
311
312 let mut evm = ctx.build_mainnet();
313 evm.transact_commit(TxEnv {
314 kind: TxKind::Create,
315 data: bytecode.clone(),
316 ..Default::default()
317 })
318 }
319
320 #[test]
321 fn test_eip3860_initcode_size_limit_failure() {
322 let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE + 1];
323 let bytecode: Bytes = large_bytecode.into();
324 let result = deploy_contract(bytecode);
325 assert!(matches!(
326 result,
327 Err(EVMError::Transaction(
328 InvalidTransaction::CreateInitCodeSizeLimit
329 ))
330 ));
331 }
332
333 #[test]
334 fn test_eip3860_initcode_size_limit_success() {
335 let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE];
336 let bytecode: Bytes = large_bytecode.into();
337 let result = deploy_contract(bytecode);
338 assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
339 }
340
341 #[test]
342 fn test_eip170_code_size_limit_failure() {
343 // use the simplest method to return a contract code size greater than 0x6000
344 // PUSH3 0x6001 (greater than 0x6000) - return size
345 // PUSH1 0x00 - memory position 0
346 // RETURN - return uninitialized memory, will be filled with 0
347 let init_code = vec![
348 0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (greater than 0x6000)
349 0x60, 0x00, // PUSH1 0
350 0xf3, // RETURN
351 ];
352 let bytecode: Bytes = init_code.into();
353 let result = deploy_contract(bytecode);
354 assert!(matches!(
355 result,
356 Ok(ExecutionResult::Halt {
357 reason: HaltReason::CreateContractSizeLimit,
358 ..
359 },)
360 ));
361 }
362
363 #[test]
364 fn test_eip170_code_size_limit_success() {
365 // use the simplest method to return a contract code size equal to 0x6000
366 // PUSH3 0x6000 - return size
367 // PUSH1 0x00 - memory position 0
368 // RETURN - return uninitialized memory, will be filled with 0
369 let init_code = vec![
370 0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000
371 0x60, 0x00, // PUSH1 0
372 0xf3, // RETURN
373 ];
374 let bytecode: Bytes = init_code.into();
375 let result = deploy_contract(bytecode);
376 assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
377 }
378
379 #[test]
380 fn test_eip170_create_opcode_size_limit_failure() {
381 // 1. create a "factory" contract, which will use the CREATE opcode to create another large contract
382 // 2. because the sub contract exceeds the EIP-170 limit, the CREATE operation should fail
383
384 // the bytecode of the factory contract:
385 // PUSH1 0x01 - the value for MSTORE
386 // PUSH1 0x00 - the memory position
387 // MSTORE - store a non-zero value at the beginning of memory
388
389 // PUSH3 0x6001 - the return size (exceeds 0x6000)
390 // PUSH1 0x00 - the memory offset
391 // PUSH1 0x00 - the amount of ETH sent
392 // CREATE - create contract instruction (create contract from current memory)
393
394 // PUSH1 0x00 - the return value storage position
395 // MSTORE - store the address returned by CREATE to the memory position 0
396 // PUSH1 0x20 - the return size (32 bytes)
397 // PUSH1 0x00 - the return offset
398 // RETURN - return the result
399
400 let factory_code = vec![
401 // 1. store a non-zero value at the beginning of memory
402 0x60, 0x01, // PUSH1 0x01
403 0x60, 0x00, // PUSH1 0x00
404 0x52, // MSTORE
405 // 2. prepare to create a large contract
406 0x62, 0x00, 0x60, 0x01, // PUSH3 0x6001 (exceeds 0x6000)
407 0x60, 0x00, // PUSH1 0x00 (the memory offset)
408 0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
409 0xf0, // CREATE
410 // 3. store the address returned by CREATE to the memory position 0
411 0x60, 0x00, // PUSH1 0x00
412 0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
413 // 4. return the result
414 0x60, 0x20, // PUSH1 0x20 (32 bytes)
415 0x60, 0x00, // PUSH1 0x00
416 0xf3, // RETURN
417 ];
418
419 // deploy factory contract
420 let factory_bytecode: Bytes = factory_code.into();
421 let factory_result =
422 deploy_contract(factory_bytecode).expect("factory contract deployment failed");
423
424 // get factory contract address
425 let factory_address = match &factory_result {
426 ExecutionResult::Success { output, .. } => match output {
427 Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
428 },
429 _ => panic!("factory contract deployment failed"),
430 };
431
432 // call factory contract to create sub contract
433 let tx_caller = address!("0x0000000000000000000000000000000000100000");
434 let call_result = Context::mainnet()
435 .with_db(CacheDB::<EmptyDB>::default())
436 .build_mainnet()
437 .transact_commit(TxEnv {
438 caller: tx_caller,
439 kind: TxKind::Call(factory_address),
440 data: Bytes::new(),
441 ..Default::default()
442 })
443 .expect("call factory contract failed");
444
445 match &call_result {
446 ExecutionResult::Success { output, .. } => match output {
447 Output::Call(bytes) => {
448 if !bytes.is_empty() {
449 assert!(
450 bytes.iter().all(|&b| b == 0),
451 "When CREATE operation failed, it should return all zero address"
452 );
453 }
454 }
455 _ => panic!("unexpected output type"),
456 },
457 _ => panic!("execution result is not Success"),
458 }
459 }
460
461 #[test]
462 fn test_eip170_create_opcode_size_limit_success() {
463 // 1. create a "factory" contract, which will use the CREATE opcode to create another contract
464 // 2. the sub contract generated by the factory contract does not exceed the EIP-170 limit, so it should be created successfully
465
466 // the bytecode of the factory contract:
467 // PUSH1 0x01 - the value for MSTORE
468 // PUSH1 0x00 - the memory position
469 // MSTORE - store a non-zero value at the beginning of memory
470
471 // PUSH3 0x6000 - the return size (0x6000)
472 // PUSH1 0x00 - the memory offset
473 // PUSH1 0x00 - the amount of ETH sent
474 // CREATE - create contract instruction (create contract from current memory)
475
476 // PUSH1 0x00 - the return value storage position
477 // MSTORE - store the address returned by CREATE to the memory position 0
478 // PUSH1 0x20 - the return size (32 bytes)
479 // PUSH1 0x00 - the return offset
480 // RETURN - return the result
481
482 let factory_code = vec![
483 // 1. store a non-zero value at the beginning of memory
484 0x60, 0x01, // PUSH1 0x01
485 0x60, 0x00, // PUSH1 0x00
486 0x52, // MSTORE
487 // 2. prepare to create a contract
488 0x62, 0x00, 0x60, 0x00, // PUSH3 0x6000 (0x6000)
489 0x60, 0x00, // PUSH1 0x00 (the memory offset)
490 0x60, 0x00, // PUSH1 0x00 (the amount of ETH sent)
491 0xf0, // CREATE
492 // 3. store the address returned by CREATE to the memory position 0
493 0x60, 0x00, // PUSH1 0x00
494 0x52, // MSTORE (store the address returned by CREATE to the memory position 0)
495 // 4. return the result
496 0x60, 0x20, // PUSH1 0x20 (32 bytes)
497 0x60, 0x00, // PUSH1 0x00
498 0xf3, // RETURN
499 ];
500
501 // deploy factory contract
502 let factory_bytecode: Bytes = factory_code.into();
503 let factory_result =
504 deploy_contract(factory_bytecode).expect("factory contract deployment failed");
505 // get factory contract address
506 let factory_address = match &factory_result {
507 ExecutionResult::Success { output, .. } => match output {
508 Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
509 },
510 _ => panic!("factory contract deployment failed"),
511 };
512
513 // call factory contract to create sub contract
514 let tx_caller = address!("0x0000000000000000000000000000000000100000");
515 let call_result = Context::mainnet()
516 .with_db(CacheDB::<EmptyDB>::default())
517 .build_mainnet()
518 .transact_commit(TxEnv {
519 caller: tx_caller,
520 kind: TxKind::Call(factory_address),
521 data: Bytes::new(),
522 ..Default::default()
523 })
524 .expect("call factory contract failed");
525
526 match &call_result {
527 ExecutionResult::Success { output, .. } => {
528 match output {
529 Output::Call(bytes) => {
530 // check if CREATE operation is successful (return non-zero address)
531 if !bytes.is_empty() {
532 assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
533 }
534 }
535 _ => panic!("unexpected output type"),
536 }
537 }
538 _ => panic!("execution result is not Success"),
539 }
540 }
541}