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